Skip to main content

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)]
506mod tests {
507    use intern_lang::Interner;
508    use source_lang::SourceMap;
509
510    use super::*;
511
512    /// Builds a graph plus the interner and source map its tests share.
513    fn fixture() -> (ModuleGraph<u32>, Interner, SourceMap) {
514        (ModuleGraph::new(), Interner::new(), SourceMap::new())
515    }
516
517    #[test]
518    fn test_add_module_assigns_sequential_stable_ids() {
519        let (mut g, mut n, mut s) = fixture();
520        let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
521        let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
522        assert_eq!(a.to_u32(), 0);
523        assert_eq!(b.to_u32(), 1);
524        assert_eq!(g.len(), 2);
525        // The first id still names the first module after more are added.
526        assert_eq!(g.module_name(a), Some(n.intern("a")));
527    }
528
529    #[test]
530    fn test_define_then_resolve_returns_value() {
531        let (mut g, mut n, mut s) = fixture();
532        let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
533        let x = n.intern("x");
534        g.define(m, x, Visibility::Public, 99).expect("unique");
535        assert_eq!(g.resolve(m, x), Ok(&99));
536    }
537
538    #[test]
539    fn test_define_duplicate_in_same_module_is_error() {
540        let (mut g, mut n, mut s) = fixture();
541        let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
542        let x = n.intern("x");
543        g.define(m, x, Visibility::Public, 1)
544            .expect("first is unique");
545        assert_eq!(
546            g.define(m, x, Visibility::Public, 2),
547            Err(ResolveError::DuplicateName { module: m, name: x }),
548        );
549    }
550
551    #[test]
552    fn test_import_resolves_through_to_definition() {
553        let (mut g, mut n, mut s) = fixture();
554        let lib = g.add_module(n.intern("lib"), s.add("lib", "").expect("fits"));
555        let app = g.add_module(n.intern("app"), s.add("app", "").expect("fits"));
556        let item = n.intern("item");
557        g.define(lib, item, Visibility::Public, 7).expect("unique");
558        g.import(app, lib, item).expect("unique");
559        assert_eq!(g.resolve(app, item), Ok(&7));
560    }
561
562    #[test]
563    fn test_resolve_unknown_name_is_unresolved() {
564        let (mut g, mut n, mut s) = fixture();
565        let m = g.add_module(n.intern("m"), s.add("m", "").expect("fits"));
566        let gone = n.intern("gone");
567        assert_eq!(
568            g.resolve(m, gone),
569            Err(ResolveError::Unresolved {
570                module: m,
571                name: gone
572            }),
573        );
574    }
575
576    #[test]
577    fn test_private_definition_is_local_only() {
578        let (mut g, mut n, mut s) = fixture();
579        let lib = g.add_module(n.intern("lib"), s.add("lib", "").expect("fits"));
580        let app = g.add_module(n.intern("app"), s.add("app", "").expect("fits"));
581        let secret = n.intern("secret");
582        g.define(lib, secret, Visibility::Private, 5)
583            .expect("unique");
584        // Visible at home.
585        assert_eq!(g.resolve(lib, secret), Ok(&5));
586        // Not through an import.
587        g.import(app, lib, secret).expect("unique");
588        assert_eq!(
589            g.resolve(app, secret),
590            Err(ResolveError::Private {
591                module: lib,
592                name: secret
593            }),
594        );
595    }
596
597    #[test]
598    fn test_import_cycle_is_reported_not_looped() {
599        let (mut g, mut n, mut s) = fixture();
600        let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
601        let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
602        let x = n.intern("x");
603        // a imports x from b, b imports x from a: a closed loop with no definition.
604        g.import(a, b, x).expect("unique");
605        g.import(b, a, x).expect("unique");
606        assert!(matches!(
607            g.resolve(a, x),
608            Err(ResolveError::ImportCycle { .. })
609        ));
610    }
611
612    #[test]
613    fn test_self_import_is_a_cycle() {
614        let (mut g, mut n, mut s) = fixture();
615        let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
616        let x = n.intern("x");
617        g.import(a, a, x).expect("unique");
618        assert!(matches!(
619            g.resolve(a, x),
620            Err(ResolveError::ImportCycle { .. })
621        ));
622    }
623
624    #[test]
625    fn test_unknown_module_id_is_error_not_panic() {
626        let (mut g, mut n, mut s) = fixture();
627        let real = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
628        // An id from a second, separate graph does not index this one.
629        let mut other: ModuleGraph<u32> = ModuleGraph::new();
630        let mut s2 = SourceMap::new();
631        let foreign = other.add_module(n.intern("z"), s2.add("z", "").expect("fits"));
632        let _ = foreign;
633        // `real` has index 0; an out-of-range id is rejected.
634        let beyond = ModuleId::from_index(g.len() as u32 + 5);
635        let x = n.intern("x");
636        assert_eq!(
637            g.resolve(beyond, x),
638            Err(ResolveError::UnknownModule(beyond))
639        );
640        assert!(g.resolve(real, x).is_err());
641    }
642
643    #[test]
644    fn test_re_export_chain_resolves_to_origin() {
645        let (mut g, mut n, mut s) = fixture();
646        let c = g.add_module(n.intern("c"), s.add("c", "").expect("fits"));
647        let b = g.add_module(n.intern("b"), s.add("b", "").expect("fits"));
648        let a = g.add_module(n.intern("a"), s.add("a", "").expect("fits"));
649        let item = n.intern("item");
650        g.define(c, item, Visibility::Public, 123).expect("unique");
651        g.import(b, c, item).expect("unique"); // b re-exports c::item
652        g.import(a, b, item).expect("unique"); // a imports through b
653        assert_eq!(g.resolve(a, item), Ok(&123));
654    }
655}