module_lang/graph.rs
1//! The module graph and name resolution.
2
3use alloc::collections::BTreeMap;
4use alloc::collections::btree_map::Entry as MapEntry;
5use alloc::vec::Vec;
6
7use source_lang::SourceId;
8use symbol_lang::Symbol;
9
10use crate::error::ResolveError;
11use crate::id::ModuleId;
12
13/// Whether an item is visible outside the module that declares it.
14///
15/// Visibility gates *cross-module* access only: a module can always reach its own
16/// names, public or private, but an [`import`](ModuleGraph::import) from another
17/// module reaches a name only if it is [`Public`](Self::Public). A
18/// [`Private`](Self::Private) name reached through an import resolves to
19/// [`ResolveError::Private`].
20///
21/// # Examples
22///
23/// ```
24/// use intern_lang::Interner;
25/// use module_lang::{ModuleGraph, ResolveError, Visibility};
26/// use source_lang::SourceMap;
27///
28/// let mut sources = SourceMap::new();
29/// let mut names = Interner::new();
30/// let mut graph: ModuleGraph<()> = ModuleGraph::new();
31///
32/// let lib = graph.add_module(names.intern("lib"), sources.add("lib", "").expect("fits"));
33/// let app = graph.add_module(names.intern("app"), sources.add("app", "").expect("fits"));
34/// let secret = names.intern("secret");
35///
36/// graph.define(lib, secret, Visibility::Private, ())?;
37///
38/// // `lib` sees its own private name.
39/// assert!(graph.resolve(lib, secret).is_ok());
40///
41/// // `app` imports it, but the import cannot reach a private name.
42/// graph.import(app, lib, secret)?;
43/// assert!(matches!(graph.resolve(app, secret), Err(ResolveError::Private { .. })));
44/// # Ok::<(), module_lang::ResolveError>(())
45/// ```
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
48pub enum Visibility {
49 /// Resolvable from other modules through an import.
50 Public,
51 /// Resolvable only from within the module that declares it.
52 Private,
53}
54
55/// One named entry in a module: either a definition made here, or a name imported
56/// from another module under the same spelling.
57#[derive(Clone, Debug)]
58enum Entry<T> {
59 /// A name defined in this module, with its visibility and payload.
60 Define {
61 /// Whether the definition is reachable from other modules.
62 visibility: Visibility,
63 /// The language-supplied payload the name resolves to.
64 value: T,
65 },
66 /// A name brought into this module from another by `import`, keeping its
67 /// spelling. Resolution follows `from` to where the name is defined.
68 Import {
69 /// The module the name is imported from.
70 from: ModuleId,
71 },
72}
73
74/// A single module: where it came from, and the names it declares.
75#[derive(Clone, Debug)]
76struct Module<T> {
77 name: Symbol,
78 source: SourceId,
79 entries: BTreeMap<Symbol, Entry<T>>,
80}
81
82/// A set of modules and the import edges between them, against which names are
83/// resolved.
84///
85/// `ModuleGraph<T>` is the owner of resolution state. Each module is added with a
86/// name and the [`SourceId`] of the file it came from, then has names
87/// [`define`](Self::define)d in it and names [`import`](Self::import)ed into it
88/// from other modules. [`resolve`](Self::resolve) answers the question every
89/// `use`/`import` asks — *which item does this name refer to?* — returning the
90/// language-supplied payload `T`, or a [`ResolveError`] for a name that is missing,
91/// private, or part of an import cycle.
92///
93/// `T` is whatever the language wants a name to resolve to — a definition id, an
94/// AST node handle, a type — and carries no trait bounds. A module's namespace is
95/// flat: a name is declared at most once per module, whether by definition or
96/// import. Names are keyed in a [`BTreeMap`], so a lookup compares interned-symbol
97/// integers in `O(log items)` and iteration is deterministic.
98///
99/// # Examples
100///
101/// ```
102/// use intern_lang::Interner;
103/// use module_lang::{ModuleGraph, Visibility};
104/// use source_lang::SourceMap;
105///
106/// // Two files, each a module.
107/// let mut sources = SourceMap::new();
108/// let app_src = sources.add("app.lang", "use util.helper;").expect("fits");
109/// let util_src = sources.add("util.lang", "pub fn helper() {}").expect("fits");
110///
111/// let mut names = Interner::new();
112/// let helper = names.intern("helper");
113///
114/// // A module per file; `util` exports `helper`; `app` imports it.
115/// let mut graph: ModuleGraph<&str> = ModuleGraph::new();
116/// let app = graph.add_module(names.intern("app"), app_src);
117/// let util = graph.add_module(names.intern("util"), util_src);
118/// graph.define(util, helper, Visibility::Public, "fn helper")?;
119/// graph.import(app, util, helper)?;
120///
121/// // `helper` resolves from `app` through the import to its definition in `util`.
122/// assert_eq!(graph.resolve(app, helper), Ok(&"fn helper"));
123/// # Ok::<(), module_lang::ResolveError>(())
124/// ```
125#[derive(Clone, Debug)]
126pub struct ModuleGraph<T> {
127 modules: Vec<Module<T>>,
128}
129
130impl<T> Default for ModuleGraph<T> {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl<T> ModuleGraph<T> {
137 /// Creates an empty graph.
138 ///
139 /// # Examples
140 ///
141 /// ```
142 /// use module_lang::ModuleGraph;
143 ///
144 /// let graph: ModuleGraph<()> = ModuleGraph::new();
145 /// assert!(graph.is_empty());
146 /// ```
147 #[must_use]
148 pub const fn new() -> Self {
149 Self {
150 modules: Vec::new(),
151 }
152 }
153
154 /// Creates an empty graph with room for `modules` modules before reallocating.
155 ///
156 /// A hint only: the graph still grows as needed. Useful when the file count is
157 /// known up front, to add every module without an intermediate reallocation.
158 ///
159 /// # Examples
160 ///
161 /// ```
162 /// use module_lang::ModuleGraph;
163 ///
164 /// let graph: ModuleGraph<()> = ModuleGraph::with_capacity(64);
165 /// assert!(graph.is_empty());
166 /// ```
167 #[must_use]
168 pub fn with_capacity(modules: usize) -> Self {
169 Self {
170 modules: Vec::with_capacity(modules),
171 }
172 }
173
174 /// Adds a module with a display `name` and the `source` it was read from,
175 /// returning its stable [`ModuleId`].
176 ///
177 /// The id is the module's insertion order and never changes; hold it to define
178 /// names in the module, import from it, or resolve against it. The `name` is
179 /// for diagnostics and the `source` records which file the module came from —
180 /// neither participates in resolution, which is always by id.
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use intern_lang::Interner;
186 /// use module_lang::ModuleGraph;
187 /// use source_lang::SourceMap;
188 ///
189 /// let mut sources = SourceMap::new();
190 /// let mut names = Interner::new();
191 /// let mut graph: ModuleGraph<()> = ModuleGraph::new();
192 ///
193 /// let src = sources.add("m.lang", "").expect("fits");
194 /// let m = graph.add_module(names.intern("m"), src);
195 /// assert_eq!(graph.len(), 1);
196 /// assert_eq!(graph.module_source(m), Some(src));
197 /// ```
198 pub fn add_module(&mut self, name: Symbol, source: SourceId) -> ModuleId {
199 let index = self.modules.len();
200 // A `Vec` of modules exhausts memory long before this index could overflow
201 // `u32` (each module is many bytes; `u32::MAX` of them is far past any real
202 // address space), so the cast below cannot wrap in practice.
203 debug_assert!(
204 index < u32::MAX as usize,
205 "module count exceeds u32 addressing"
206 );
207 self.modules.push(Module {
208 name,
209 source,
210 entries: BTreeMap::new(),
211 });
212 ModuleId::from_index(index as u32)
213 }
214
215 /// Defines `name` in `module` with a visibility and a payload.
216 ///
217 /// Returns [`ResolveError::DuplicateName`] if the module already declares the
218 /// name (by an earlier definition or an import), and
219 /// [`ResolveError::UnknownModule`] if `module` was not minted by this graph. On
220 /// success the name resolves to `value` from within the module immediately, and
221 /// from other modules that import it when it is [`Visibility::Public`].
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// use intern_lang::Interner;
227 /// use module_lang::{ModuleGraph, ResolveError, Visibility};
228 /// use source_lang::SourceMap;
229 ///
230 /// let mut sources = SourceMap::new();
231 /// let mut names = Interner::new();
232 /// let mut graph: ModuleGraph<i32> = ModuleGraph::new();
233 ///
234 /// let m = graph.add_module(names.intern("m"), sources.add("m", "").expect("fits"));
235 /// let x = names.intern("x");
236 ///
237 /// graph.define(m, x, Visibility::Public, 42)?;
238 /// assert_eq!(graph.resolve(m, x), Ok(&42));
239 ///
240 /// // A second definition of the same name in the same module is rejected.
241 /// assert!(matches!(
242 /// graph.define(m, x, Visibility::Private, 7),
243 /// Err(ResolveError::DuplicateName { .. }),
244 /// ));
245 /// # Ok::<(), module_lang::ResolveError>(())
246 /// ```
247 pub fn define(
248 &mut self,
249 module: ModuleId,
250 name: Symbol,
251 visibility: Visibility,
252 value: T,
253 ) -> Result<(), ResolveError> {
254 let target = self.module_mut(module)?;
255 match target.entries.entry(name) {
256 MapEntry::Occupied(_) => Err(ResolveError::DuplicateName { module, name }),
257 MapEntry::Vacant(slot) => {
258 let _ = slot.insert(Entry::Define { visibility, value });
259 Ok(())
260 }
261 }
262 }
263
264 /// Imports `name` into `into` from the `from` module, keeping its spelling.
265 ///
266 /// This records an import edge; it does not require `name` to be defined in
267 /// `from` yet, so a graph can be built in any order. Whether the import
268 /// actually reaches a public definition is determined when the name is
269 /// [`resolve`](Self::resolve)d.
270 ///
271 /// Returns [`ResolveError::DuplicateName`] if `into` already declares `name`,
272 /// and [`ResolveError::UnknownModule`] if either id was not minted by this
273 /// graph.
274 ///
275 /// # Examples
276 ///
277 /// ```
278 /// use intern_lang::Interner;
279 /// use module_lang::{ModuleGraph, Visibility};
280 /// use source_lang::SourceMap;
281 ///
282 /// let mut sources = SourceMap::new();
283 /// let mut names = Interner::new();
284 /// let mut graph: ModuleGraph<()> = ModuleGraph::new();
285 ///
286 /// let lib = graph.add_module(names.intern("lib"), sources.add("lib", "").expect("fits"));
287 /// let app = graph.add_module(names.intern("app"), sources.add("app", "").expect("fits"));
288 /// let item = names.intern("item");
289 ///
290 /// graph.define(lib, item, Visibility::Public, ())?;
291 /// graph.import(app, lib, item)?;
292 /// assert!(graph.resolve(app, item).is_ok());
293 /// # Ok::<(), module_lang::ResolveError>(())
294 /// ```
295 pub fn import(
296 &mut self,
297 into: ModuleId,
298 from: ModuleId,
299 name: Symbol,
300 ) -> Result<(), ResolveError> {
301 if from.to_index() >= self.modules.len() {
302 return Err(ResolveError::UnknownModule(from));
303 }
304 let target = self.module_mut(into)?;
305 match target.entries.entry(name) {
306 MapEntry::Occupied(_) => Err(ResolveError::DuplicateName { module: into, name }),
307 MapEntry::Vacant(slot) => {
308 let _ = slot.insert(Entry::Import { from });
309 Ok(())
310 }
311 }
312 }
313
314 /// Resolves `name` as seen from within `module`, returning the payload it
315 /// refers to.
316 ///
317 /// A name defined in the module wins, public or private. A name imported into
318 /// the module is followed to its source module — and onward along any chain of
319 /// re-imports — to the public definition it names. The failure modes are all
320 /// defined [`ResolveError`]s:
321 ///
322 /// - [`Unresolved`](ResolveError::Unresolved) — the name is declared nowhere on
323 /// the path.
324 /// - [`Private`](ResolveError::Private) — an import reached a name private to
325 /// another module.
326 /// - [`ImportCycle`](ResolveError::ImportCycle) — the import chain loops.
327 /// - [`UnknownModule`](ResolveError::UnknownModule) — `module` is not from this
328 /// graph.
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// use intern_lang::Interner;
334 /// use module_lang::{ModuleGraph, ResolveError, Visibility};
335 /// use source_lang::SourceMap;
336 ///
337 /// let mut sources = SourceMap::new();
338 /// let mut names = Interner::new();
339 /// let mut graph: ModuleGraph<&str> = ModuleGraph::new();
340 ///
341 /// let m = graph.add_module(names.intern("m"), sources.add("m", "").expect("fits"));
342 /// let here = names.intern("here");
343 /// let gone = names.intern("gone");
344 ///
345 /// graph.define(m, here, Visibility::Public, "local")?;
346 /// assert_eq!(graph.resolve(m, here), Ok(&"local"));
347 /// assert!(matches!(graph.resolve(m, gone), Err(ResolveError::Unresolved { .. })));
348 /// # Ok::<(), module_lang::ResolveError>(())
349 /// ```
350 pub fn resolve(&self, module: ModuleId, name: Symbol) -> Result<&T, ResolveError> {
351 let source = self.module(module)?;
352 let from = match source.entries.get(&name) {
353 None => return Err(ResolveError::Unresolved { module, name }),
354 // A name defined here resolves directly, regardless of visibility: a
355 // module can always see its own names.
356 Some(Entry::Define { value, .. }) => return Ok(value),
357 Some(Entry::Import { from }) => *from,
358 };
359 let mut visited = Vec::new();
360 visited.push((module.to_u32(), name.as_u32()));
361 self.resolve_export(from, name, &mut visited)
362 }
363
364 /// Number of modules in the graph.
365 ///
366 /// # Examples
367 ///
368 /// ```
369 /// use intern_lang::Interner;
370 /// use module_lang::ModuleGraph;
371 /// use source_lang::SourceMap;
372 ///
373 /// let mut sources = SourceMap::new();
374 /// let mut names = Interner::new();
375 /// let mut graph: ModuleGraph<()> = ModuleGraph::new();
376 /// assert_eq!(graph.len(), 0);
377 /// graph.add_module(names.intern("m"), sources.add("m", "").expect("fits"));
378 /// assert_eq!(graph.len(), 1);
379 /// ```
380 #[must_use]
381 pub fn len(&self) -> usize {
382 self.modules.len()
383 }
384
385 /// Whether the graph holds no modules.
386 ///
387 /// # Examples
388 ///
389 /// ```
390 /// use module_lang::ModuleGraph;
391 ///
392 /// let graph: ModuleGraph<()> = ModuleGraph::new();
393 /// assert!(graph.is_empty());
394 /// ```
395 #[must_use]
396 pub fn is_empty(&self) -> bool {
397 self.modules.is_empty()
398 }
399
400 /// The display name a module was added with, or `None` if `module` is not from
401 /// this graph.
402 ///
403 /// # Examples
404 ///
405 /// ```
406 /// use intern_lang::Interner;
407 /// use module_lang::ModuleGraph;
408 /// use source_lang::SourceMap;
409 ///
410 /// let mut sources = SourceMap::new();
411 /// let mut names = Interner::new();
412 /// let mut graph: ModuleGraph<()> = ModuleGraph::new();
413 /// let name = names.intern("m");
414 /// let m = graph.add_module(name, sources.add("m", "").expect("fits"));
415 /// assert_eq!(graph.module_name(m), Some(name));
416 /// ```
417 #[must_use]
418 pub fn module_name(&self, module: ModuleId) -> Option<Symbol> {
419 self.modules.get(module.to_index()).map(|m| m.name)
420 }
421
422 /// The [`SourceId`] a module was added with, or `None` if `module` is not from
423 /// this graph.
424 ///
425 /// # Examples
426 ///
427 /// ```
428 /// use intern_lang::Interner;
429 /// use module_lang::ModuleGraph;
430 /// use source_lang::SourceMap;
431 ///
432 /// let mut sources = SourceMap::new();
433 /// let mut names = Interner::new();
434 /// let mut graph: ModuleGraph<()> = ModuleGraph::new();
435 /// let src = sources.add("m", "").expect("fits");
436 /// let m = graph.add_module(names.intern("m"), src);
437 /// assert_eq!(graph.module_source(m), Some(src));
438 /// ```
439 #[must_use]
440 pub fn module_source(&self, module: ModuleId) -> Option<SourceId> {
441 self.modules.get(module.to_index()).map(|m| m.source)
442 }
443
444 /// Borrows a module by id, or reports it as unknown.
445 fn module(&self, id: ModuleId) -> Result<&Module<T>, ResolveError> {
446 self.modules
447 .get(id.to_index())
448 .ok_or(ResolveError::UnknownModule(id))
449 }
450
451 /// Borrows a module mutably by id, or reports it as unknown.
452 fn module_mut(&mut self, id: ModuleId) -> Result<&mut Module<T>, ResolveError> {
453 self.modules
454 .get_mut(id.to_index())
455 .ok_or(ResolveError::UnknownModule(id))
456 }
457
458 /// Resolves `name` as a public export of `start`, following any chain of
459 /// re-imports to the definition it names.
460 ///
461 /// `visited` carries the `(module, name)` pairs already seen so a chain that
462 /// loops back is reported as a cycle rather than recursing without end. The
463 /// walk is iterative and copies module ids out of the graph (phase one) so the
464 /// terminal definition can be borrowed in a single step (phase two) without
465 /// returning a borrow across the loop.
466 fn resolve_export(
467 &self,
468 start: ModuleId,
469 name: Symbol,
470 visited: &mut Vec<(u32, u32)>,
471 ) -> Result<&T, ResolveError> {
472 let mut module = start;
473 let terminal = loop {
474 let key = (module.to_u32(), name.as_u32());
475 if visited.contains(&key) {
476 return Err(ResolveError::ImportCycle { module, name });
477 }
478 visited.push(key);
479 match self.module(module)?.entries.get(&name) {
480 None => return Err(ResolveError::Unresolved { module, name }),
481 Some(Entry::Define {
482 visibility: Visibility::Public,
483 ..
484 }) => break module,
485 Some(Entry::Define {
486 visibility: Visibility::Private,
487 ..
488 }) => return Err(ResolveError::Private { module, name }),
489 Some(Entry::Import { from }) => module = *from,
490 }
491 };
492 match self.module(terminal)?.entries.get(&name) {
493 Some(Entry::Define { value, .. }) => Ok(value),
494 // Phase one ended on a public definition for `(terminal, name)` and the
495 // graph cannot have changed through `&self`, so this arm does not occur
496 // in practice; it returns a defined error rather than panicking.
497 _ => Err(ResolveError::Unresolved {
498 module: terminal,
499 name,
500 }),
501 }
502 }
503}
504
505#[cfg(test)]
506#[allow(
507 clippy::expect_used,
508 reason = "unwrapping a known-good setup is fine in tests"
509)]
510mod tests {
511 use intern_lang::Interner;
512 use source_lang::SourceMap;
513
514 use super::*;
515
516 /// Builds a graph plus the interner and source map its tests share.
517 fn fixture() -> (ModuleGraph<u32>, Interner, SourceMap) {
518 (ModuleGraph::new(), Interner::new(), SourceMap::new())
519 }
520
521 #[test]
522 fn test_add_module_assigns_sequential_stable_ids() {
523 let (mut g, mut n, mut s) = fixture();
524 let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
525 let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
526 assert_eq!(a.to_u32(), 0);
527 assert_eq!(b.to_u32(), 1);
528 assert_eq!(g.len(), 2);
529 // The first id still names the first module after more are added.
530 assert_eq!(g.module_name(a), Some(n.intern("a")));
531 }
532
533 #[test]
534 fn test_define_then_resolve_returns_value() {
535 let (mut g, mut n, mut s) = fixture();
536 let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
537 let x = n.intern("x");
538 g.define(m, x, Visibility::Public, 99).expect("unique");
539 assert_eq!(g.resolve(m, x), Ok(&99));
540 }
541
542 #[test]
543 fn test_define_duplicate_in_same_module_is_error() {
544 let (mut g, mut n, mut s) = fixture();
545 let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
546 let x = n.intern("x");
547 g.define(m, x, Visibility::Public, 1)
548 .expect("first is unique");
549 assert_eq!(
550 g.define(m, x, Visibility::Public, 2),
551 Err(ResolveError::DuplicateName { module: m, name: x }),
552 );
553 }
554
555 #[test]
556 fn test_import_resolves_through_to_definition() {
557 let (mut g, mut n, mut s) = fixture();
558 let lib = g.add_module(n.intern("lib"), s.add("lib", "").expect("fits"));
559 let app = g.add_module(n.intern("app"), s.add("app", "").expect("fits"));
560 let item = n.intern("item");
561 g.define(lib, item, Visibility::Public, 7).expect("unique");
562 g.import(app, lib, item).expect("unique");
563 assert_eq!(g.resolve(app, item), Ok(&7));
564 }
565
566 #[test]
567 fn test_resolve_unknown_name_is_unresolved() {
568 let (mut g, mut n, mut s) = fixture();
569 let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
570 let gone = n.intern("gone");
571 assert_eq!(
572 g.resolve(m, gone),
573 Err(ResolveError::Unresolved {
574 module: m,
575 name: gone
576 }),
577 );
578 }
579
580 #[test]
581 fn test_private_definition_is_local_only() {
582 let (mut g, mut n, mut s) = fixture();
583 let lib = g.add_module(n.intern("lib"), s.add("lib", "").expect("fits"));
584 let app = g.add_module(n.intern("app"), s.add("app", "").expect("fits"));
585 let secret = n.intern("secret");
586 g.define(lib, secret, Visibility::Private, 5)
587 .expect("unique");
588 // Visible at home.
589 assert_eq!(g.resolve(lib, secret), Ok(&5));
590 // Not through an import.
591 g.import(app, lib, secret).expect("unique");
592 assert_eq!(
593 g.resolve(app, secret),
594 Err(ResolveError::Private {
595 module: lib,
596 name: secret
597 }),
598 );
599 }
600
601 #[test]
602 fn test_import_cycle_is_reported_not_looped() {
603 let (mut g, mut n, mut s) = fixture();
604 let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
605 let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
606 let x = n.intern("x");
607 // a imports x from b, b imports x from a: a closed loop with no definition.
608 g.import(a, b, x).expect("unique");
609 g.import(b, a, x).expect("unique");
610 assert!(matches!(
611 g.resolve(a, x),
612 Err(ResolveError::ImportCycle { .. })
613 ));
614 }
615
616 #[test]
617 fn test_self_import_is_a_cycle() {
618 let (mut g, mut n, mut s) = fixture();
619 let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
620 let x = n.intern("x");
621 g.import(a, a, x).expect("unique");
622 assert!(matches!(
623 g.resolve(a, x),
624 Err(ResolveError::ImportCycle { .. })
625 ));
626 }
627
628 #[test]
629 fn test_unknown_module_id_is_error_not_panic() {
630 let (mut g, mut n, mut s) = fixture();
631 let real = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
632 // An id from a second, separate graph does not index this one.
633 let mut other: ModuleGraph<u32> = ModuleGraph::new();
634 let mut s2 = SourceMap::new();
635 let foreign = other.add_module(n.intern("z"), s2.add("z", "").expect("fits"));
636 let _ = foreign;
637 // `real` has index 0; an out-of-range id is rejected.
638 let beyond = ModuleId::from_index(g.len() as u32 + 5);
639 let x = n.intern("x");
640 assert_eq!(
641 g.resolve(beyond, x),
642 Err(ResolveError::UnknownModule(beyond))
643 );
644 assert!(g.resolve(real, x).is_err());
645 }
646
647 #[test]
648 fn test_re_export_chain_resolves_to_origin() {
649 let (mut g, mut n, mut s) = fixture();
650 let c = g.add_module(n.intern("c"), s.add("c", "").expect("fits"));
651 let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
652 let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
653 let item = n.intern("item");
654 g.define(c, item, Visibility::Public, 123).expect("unique");
655 g.import(b, c, item).expect("unique"); // b re-exports c::item
656 g.import(a, b, item).expect("unique"); // a imports through b
657 assert_eq!(g.resolve(a, item), Ok(&123));
658 }
659}