module_lang/error.rs
1//! The error type returned when the graph is built or a name is resolved.
2
3use core::fmt;
4
5use symbol_lang::Symbol;
6
7use crate::id::ModuleId;
8
9/// The reason a graph operation or a name resolution did not succeed.
10///
11/// Building the graph and resolving through it can fail five ways, each a
12/// distinct, defined outcome rather than a panic or a silently wrong answer:
13///
14/// - the id passed in was not minted by this graph
15/// ([`UnknownModule`](Self::UnknownModule)),
16/// - a name was declared twice in one module
17/// ([`DuplicateName`](Self::DuplicateName)),
18/// - a name is not declared in the module it was looked up in
19/// ([`Unresolved`](Self::Unresolved)),
20/// - an import targets a name that is private to the module that declares it
21/// ([`Private`](Self::Private)), or
22/// - an import chain loops back on itself ([`ImportCycle`](Self::ImportCycle)).
23///
24/// Each variant carries the [`ModuleId`] and [`Symbol`] it concerns, so a caller
25/// can recover the human-readable spelling from its own interner and build a
26/// richer diagnostic. The [`fmt::Display`] form is a terse fallback that names the
27/// raw ids — module-lang does not own the interner, so it cannot print the name.
28///
29/// The enum is `#[non_exhaustive]`: a downstream `match` must include a wildcard
30/// arm, so later additions never force a breaking change on callers.
31///
32/// # Examples
33///
34/// ```
35/// use intern_lang::Interner;
36/// use module_lang::{ModuleGraph, ResolveError, Visibility};
37/// use source_lang::SourceMap;
38///
39/// let mut sources = SourceMap::new();
40/// let mut names = Interner::new();
41/// let mut graph: ModuleGraph<()> = ModuleGraph::new();
42///
43/// let m = graph.add_module(names.intern("m"), sources.add("m", "").expect("fits"));
44/// let x = names.intern("x");
45/// graph.define(m, x, Visibility::Public, ()).expect("first definition is unique");
46///
47/// // Declaring the same name again in the same module is a defined error.
48/// assert!(matches!(
49/// graph.define(m, x, Visibility::Public, ()),
50/// Err(ResolveError::DuplicateName { .. }),
51/// ));
52///
53/// // Looking up a name that was never declared is a defined error, not a panic.
54/// let y = names.intern("y");
55/// assert!(matches!(
56/// graph.resolve(m, y),
57/// Err(ResolveError::Unresolved { .. }),
58/// ));
59/// ```
60#[derive(Clone, Debug, PartialEq, Eq)]
61#[non_exhaustive]
62pub enum ResolveError {
63 /// A [`ModuleId`] was passed that this graph never minted.
64 ///
65 /// Every id comes from
66 /// [`ModuleGraph::add_module`](crate::ModuleGraph::add_module) on the graph it
67 /// is used with; an id from a different graph, or one fabricated by other
68 /// means, lands here instead of indexing out of bounds.
69 UnknownModule(ModuleId),
70
71 /// A name was declared twice in the same module.
72 ///
73 /// Both [`define`](crate::ModuleGraph::define) and
74 /// [`import`](crate::ModuleGraph::import) reject a name already present in the
75 /// target module — a module's namespace is flat, so a definition and an import
76 /// cannot share a spelling. The conflict is reported where the second
77 /// declaration is added, not silently resolved last-write-wins.
78 DuplicateName {
79 /// The module the duplicate was added to.
80 module: ModuleId,
81 /// The name that was already declared there.
82 name: Symbol,
83 },
84
85 /// A name is not declared in the module it was looked up in.
86 ///
87 /// Returned by [`resolve`](crate::ModuleGraph::resolve) when neither a
88 /// definition nor an import in the module (or, following an import chain, in a
89 /// source module) declares the name.
90 Unresolved {
91 /// The module the name was looked up in.
92 module: ModuleId,
93 /// The name that could not be found.
94 name: Symbol,
95 },
96
97 /// An import resolved to a name that is private to the module declaring it.
98 ///
99 /// A [`Visibility::Private`](crate::Visibility::Private) definition is visible
100 /// only from within its own module; reaching it through an import from another
101 /// module is rejected here rather than silently allowed.
102 Private {
103 /// The module that declares the name privately.
104 module: ModuleId,
105 /// The private name an import tried to reach.
106 name: Symbol,
107 },
108
109 /// An import chain loops back on itself.
110 ///
111 /// Resolving an import follows it to its source module, and so on; if that walk
112 /// returns to a `(module, name)` pair it has already visited, the chain forms a
113 /// cycle. It is reported here in bounded time rather than recursing without
114 /// limit.
115 ImportCycle {
116 /// The module the walk looped back to.
117 module: ModuleId,
118 /// The name whose import chain forms the cycle.
119 name: Symbol,
120 },
121}
122
123impl fmt::Display for ResolveError {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 match self {
126 Self::UnknownModule(id) => {
127 write!(f, "module id {} was not minted by this graph", id.to_u32())
128 }
129 Self::DuplicateName { module, name } => write!(
130 f,
131 "name (symbol {}) is already declared in module {}",
132 name.as_u32(),
133 module.to_u32(),
134 ),
135 Self::Unresolved { module, name } => write!(
136 f,
137 "no name (symbol {}) is declared in module {}",
138 name.as_u32(),
139 module.to_u32(),
140 ),
141 Self::Private { module, name } => write!(
142 f,
143 "name (symbol {}) in module {} is private and cannot be imported",
144 name.as_u32(),
145 module.to_u32(),
146 ),
147 Self::ImportCycle { module, name } => write!(
148 f,
149 "import of name (symbol {}) forms a cycle at module {}",
150 name.as_u32(),
151 module.to_u32(),
152 ),
153 }
154 }
155}
156
157impl core::error::Error for ResolveError {}
158
159#[cfg(test)]
160mod tests {
161 extern crate alloc;
162 use alloc::string::ToString;
163
164 use super::*;
165
166 #[test]
167 fn test_unknown_module_display_names_the_id() {
168 let err = ResolveError::UnknownModule(ModuleId::from_index(4));
169 assert!(err.to_string().contains('4'), "{err}");
170 }
171
172 #[test]
173 fn test_duplicate_display_names_module_and_symbol() {
174 let err = ResolveError::DuplicateName {
175 module: ModuleId::from_index(2),
176 name: Symbol::from_u32(7).expect("nonzero"),
177 };
178 let text = err.to_string();
179 assert!(text.contains('2'), "{text}");
180 assert!(text.contains('7'), "{text}");
181 }
182
183 #[test]
184 fn test_error_is_clonable_and_equatable() {
185 let a = ResolveError::Unresolved {
186 module: ModuleId::from_index(1),
187 name: Symbol::from_u32(1).expect("nonzero"),
188 };
189 let b = a.clone();
190 assert_eq!(a, b);
191 }
192}