Skip to main content

harn_modules/
namespace_imports.rs

1//! Namespace import (`import * as alias from "..."`) graph APIs.
2
3use std::collections::BTreeMap;
4use std::path::{Path, PathBuf};
5
6use crate::namespace_signatures::NamespaceMemberSignature;
7use crate::package_imports::resolve_import_path_with_snapshots;
8use crate::package_snapshot::PackageSnapshot;
9use crate::{decl_site, normalize_path, DefKind, DefSite, ImportRef, ModuleGraph, ModuleInfo};
10
11/// One `import * as alias from "..."` binding visible from a consumer file.
12#[derive(Debug, Clone, PartialEq)]
13pub struct NamespaceImportInfo {
14    pub alias: String,
15    pub raw_path: String,
16    pub resolved_path: Option<PathBuf>,
17    /// Public export names from the target module (empty when unresolved).
18    pub member_names: Vec<String>,
19    /// Declaration kind for each exported member name.
20    pub member_kinds: BTreeMap<String, DefKind>,
21    /// Call signature for each callable member, as a self-contained
22    /// [`TypeExpr::FnType`] with the target module's named types already
23    /// inlined. A member absent here is not statically callable (or was
24    /// deliberately left gradual); the checker keeps its `any` treatment.
25    pub member_signatures: BTreeMap<String, NamespaceMemberSignature>,
26}
27
28impl ModuleGraph {
29    /// Namespace imports (`import * as alias from "..."`) declared by `file`.
30    ///
31    /// Returns `None` when any namespace import path is unresolved so callers
32    /// can fall back to conservative checking. Member names/kinds come from
33    /// the target module's public export surface (`exports` + `DefKind`).
34    pub fn namespace_imports_for_file(&self, file: &Path) -> Option<Vec<NamespaceImportInfo>> {
35        let file = normalize_path(file);
36        let module = self.modules.get(&file)?;
37        if module.has_unresolved_namespace_import {
38            return None;
39        }
40
41        let mut out = Vec::new();
42        for import in &module.imports {
43            let Some(alias) = &import.namespace_alias else {
44                continue;
45            };
46            let (member_names, member_kinds, member_signatures) = match &import.path {
47                Some(import_path) => {
48                    let imported = self
49                        .modules
50                        .get(import_path)
51                        .or_else(|| self.modules.get(&normalize_path(import_path)))?;
52                    if imported.load_error.is_some() {
53                        return None;
54                    }
55                    let mut names: Vec<String> = imported.exports.iter().cloned().collect();
56                    names.sort();
57                    let mut kinds = BTreeMap::new();
58                    for name in &names {
59                        if let Some(kind) = self.exported_kind(import_path, name) {
60                            kinds.insert(name.clone(), kind);
61                        }
62                    }
63                    let signatures = self.namespace_member_signatures(import_path, &names);
64                    (names, kinds, signatures)
65                }
66                None => (Vec::new(), BTreeMap::new(), BTreeMap::new()),
67            };
68            out.push(NamespaceImportInfo {
69                alias: alias.clone(),
70                raw_path: import.raw_path.clone(),
71                resolved_path: import.path.as_ref().map(|path| normalize_path(path)),
72                member_names,
73                member_kinds,
74                member_signatures,
75            });
76        }
77        Some(out)
78    }
79
80    /// Look up a member under a namespace import alias visible from `file`.
81    ///
82    /// Returns the target module's definition site for `member`, or `None`
83    /// when the alias is not a namespace import / the member is not exported.
84    pub fn namespace_member_lookup(
85        &self,
86        file: &Path,
87        alias: &str,
88        member: &str,
89    ) -> Option<DefSite> {
90        let file = normalize_path(file);
91        let module = self.modules.get(&file)?;
92        let target = module
93            .imports
94            .iter()
95            .find(|import| import.namespace_alias.as_deref() == Some(alias))
96            .and_then(|import| import.path.as_ref())
97            .or_else(|| module.namespace_re_exports.get(alias))?;
98        self.exported_kind(target, member)?;
99        self.export_definition_of(target, member)
100            .or_else(|| self.definition_of(target, member))
101    }
102}
103
104/// Record a `NamespaceImport` into `module` during graph construction.
105pub(crate) fn record_namespace_import(
106    module: &mut ModuleInfo,
107    file: &Path,
108    span: harn_lexer::Span,
109    alias: &str,
110    path: &str,
111    is_pub: bool,
112    package_snapshots: &[PackageSnapshot],
113) {
114    let import_path = resolve_import_path_with_snapshots(file, path, package_snapshots);
115    if import_path.is_none() {
116        module.has_unresolved_namespace_import = true;
117    }
118    // Bind the alias locally as a Variable. `pub import * as alias`
119    // re-exports the namespace object itself — never flatten target
120    // members into this module's public surface (contrast
121    // `wildcard_re_export_paths`).
122    module.declarations.insert(
123        alias.to_string(),
124        decl_site(file, span, alias, DefKind::Variable),
125    );
126    if is_pub {
127        module.own_exports.insert(alias.to_string());
128        module.exports.insert(alias.to_string());
129        if let Some(resolved) = &import_path {
130            module
131                .namespace_re_exports
132                .insert(alias.to_string(), normalize_path(resolved));
133        }
134    }
135    module.imports.push(ImportRef {
136        raw_path: path.to_string(),
137        path: import_path,
138        selective_names: None,
139        namespace_alias: Some(alias.to_string()),
140        is_pub,
141        import_span: span,
142    });
143}