Skip to main content

harn_modules/
stdlib.rs

1//! Embedded stdlib source access for the module graph.
2
3use std::path::{Component, Path, PathBuf};
4
5use harn_stdlib_catalog::{CatalogNotInstalled, StdlibCatalog};
6
7/// The installed stdlib catalog, or the typed reason there is none.
8///
9/// With the `bundled-stdlib` feature the bundled catalog is installed on first
10/// read; otherwise the host must have called `harn_stdlib::install()`.
11pub fn try_stdlib_catalog() -> Result<&'static StdlibCatalog, CatalogNotInstalled> {
12    #[cfg(any(test, feature = "bundled-stdlib"))]
13    let _ = harn_stdlib::install();
14    harn_stdlib_catalog::installed()
15}
16
17/// The installed stdlib catalog. Reading the stdlib without one is a host
18/// bug, so it panics with the typed reason instead of acting as if the
19/// standard library were empty.
20pub fn stdlib_catalog() -> &'static StdlibCatalog {
21    try_stdlib_catalog().unwrap_or_else(|error| panic!("{error}"))
22}
23
24/// Return the embedded stdlib source for `module` (the part after
25/// `std/`), or `None` if no stdlib module with that name exists.
26pub(crate) fn get_stdlib_source(module: &str) -> Option<&'static str> {
27    stdlib_catalog().source(module)
28}
29
30/// Rust builtins exported through an embedded stdlib module's public surface.
31pub(crate) fn builtin_reexports(module: &str) -> &'static [&'static str] {
32    harn_stdlib_catalog::builtin_reexports(module)
33}
34
35/// Sentinel path used to key embedded stdlib modules in the module
36/// graph. Real files never resolve to this path, so collisions are
37/// impossible.
38pub(crate) fn stdlib_virtual_path(module: &str) -> PathBuf {
39    PathBuf::from(format!("<std>/{module}"))
40}
41
42pub(crate) fn is_stdlib_virtual_path(path: &Path) -> bool {
43    path.to_str().is_some_and(|path| path.starts_with("<std>/"))
44}
45
46/// The canonical form of a virtual stdlib path, or `None` for an ordinary one.
47///
48/// Module-graph paths (`<std>/module`) are canonical as written. Every other
49/// embedded-stdlib spelling, such as the runtime's `<stdlib>/module.harn` or a
50/// virtual parent directory, cannot exist on disk: canonicalizing it always
51/// fails and is never memoized, so module sibling checks would call `realpath`
52/// on every import. It resolves lexically, exactly as that failure path does.
53pub(crate) fn canonical_virtual_path(path: &Path) -> Option<PathBuf> {
54    if is_stdlib_virtual_path(path) {
55        return Some(path.to_path_buf());
56    }
57    is_embedded_stdlib_spelling(path).then(|| crate::manifest_walk::normalize_lexically(path))
58}
59
60/// Whether `path` lies under either virtual stdlib root: the module graph's
61/// `<std>` or the runtime's `<stdlib>`, including the roots themselves.
62pub(crate) fn is_embedded_stdlib_spelling(path: &Path) -> bool {
63    let Some(path) = path.to_str() else {
64        return false;
65    };
66    ["<std>", "<stdlib>"].iter().any(|root| {
67        path.strip_prefix(root)
68            .is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
69    })
70}
71
72/// Resolve a relative import owned by an embedded stdlib module.
73///
74/// Embedded modules use virtual paths rather than mirror files on disk. Keep
75/// their sibling-import semantics identical to ordinary modules while refusing
76/// any path that escapes the stdlib namespace.
77pub(crate) fn relative_stdlib_module(current_file: &Path, import_path: &str) -> Option<String> {
78    let current = current_file.to_str()?.strip_prefix("<std>/")?;
79    let mut parts = current.split('/').collect::<Vec<_>>();
80    parts.pop()?;
81
82    for component in Path::new(import_path).components() {
83        match component {
84            Component::CurDir => {}
85            Component::ParentDir => {
86                parts.pop()?;
87            }
88            Component::Normal(part) => parts.push(part.to_str()?),
89            Component::RootDir | Component::Prefix(_) => return None,
90        }
91    }
92
93    let last = parts.last_mut()?;
94    *last = last.strip_suffix(".harn").unwrap_or(last);
95    let module = parts.join("/");
96    get_stdlib_source(&module).map(|_| module)
97}
98
99#[cfg(test)]
100mod tests {
101    /// The compiler crates read the stdlib through `harn-stdlib-catalog`. A
102    /// normal dependency on `harn-stdlib` would put every embedded `.harn` file
103    /// back into their build inputs, so one stdlib edit would rebuild them and
104    /// everything compiled against them again.
105    #[test]
106    fn compiler_crates_do_not_depend_on_the_embedded_sources() {
107        let crates = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
108            .parent()
109            .expect("crates directory");
110        for (crate_name, allowed_optional) in [("harn-modules", true), ("harn-vm", false)] {
111            let manifest = std::fs::read_to_string(crates.join(crate_name).join("Cargo.toml"))
112                .expect("read manifest");
113            let manifest: toml::Table = toml::from_str(&manifest).expect("parse manifest");
114            let dependency = manifest
115                .get("dependencies")
116                .and_then(|dependencies| dependencies.get("harn-stdlib"));
117            let optional = dependency
118                .and_then(|dependency| dependency.get("optional"))
119                .and_then(toml::Value::as_bool)
120                == Some(true);
121            assert!(
122                dependency.is_none() || (allowed_optional && optional),
123                "{crate_name} must not depend on harn-stdlib; read the stdlib through harn-stdlib-catalog"
124            );
125        }
126    }
127}