Skip to main content

harn_modules/
construction.rs

1//! Source retention and projections for the shared graph walk.
2
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6use harn_parser::SNode;
7
8use super::{
9    build_inner, normalize_path, ModuleGraph, ModuleGraphBuild, PackageContext,
10    ParsedSourceRetention,
11};
12
13/// Build a module graph from a set of files.
14///
15/// Files referenced via `import` statements are loaded recursively so the
16/// graph contains every module reachable from the seed set. Cycles and
17/// already-loaded files are skipped via a visited set.
18pub fn build(files: &[PathBuf]) -> ModuleGraph {
19    build_inner(
20        files,
21        ParsedSourceRetention::None,
22        None,
23        PackageContext::Project,
24        None,
25    )
26    .graph
27}
28
29/// Build a module graph while retaining parsed sources for the seed files.
30///
31/// Imported-only modules still participate in the graph, but their ASTs are
32/// dropped after graph extraction so callers do not pay extra peak memory for
33/// parsed sources they will not reuse.
34pub fn build_with_parsed_sources(files: &[PathBuf]) -> ModuleGraphBuild {
35    let parsed_source_targets = files.iter().map(|file| normalize_path(file)).collect();
36    build_inner(
37        files,
38        ParsedSourceRetention::Seeds(&parsed_source_targets),
39        None,
40        PackageContext::Project,
41        None,
42    )
43}
44
45/// Build a closed module graph while retaining every reachable parsed source.
46///
47/// This is intentionally separate from editor-oriented graph construction:
48/// package linking consumes every module AST, while ordinary diagnostics should
49/// not retain an entire dependency graph after extracting its public surface.
50pub fn build_closed_program(files: &[PathBuf]) -> ModuleGraphBuild {
51    build_inner(
52        files,
53        ParsedSourceRetention::All,
54        None,
55        PackageContext::Project,
56        None,
57    )
58}
59
60/// Project each readable module before its source and syntax tree are dropped.
61///
62/// The callback runs once per canonical module, including imported modules,
63/// and may run concurrently on parse workers. It borrows the same source and
64/// program used to extract the graph. Retain only the facts the consumer needs
65/// instead of keeping every reachable syntax tree alive until the walk ends.
66/// `program` is `None` when lexing or parsing failed; the original source is
67/// still available so consumers can preserve that failure's source identity.
68pub fn build_with_module_projection<T: Send>(
69    files: &[PathBuf],
70    project: impl Fn(&Path, &str, Option<&[SNode]>) -> T + Sync,
71) -> (ModuleGraph, HashMap<PathBuf, T>) {
72    let projections = std::sync::Mutex::new(HashMap::new());
73    let observer = |path: &Path, source: &str, program: Option<&[SNode]>| {
74        let projection = project(path, source, program);
75        projections
76            .lock()
77            .expect("module projection lock poisoned")
78            .insert(path.to_path_buf(), projection);
79    };
80    let graph = build_inner(
81        files,
82        ParsedSourceRetention::None,
83        None,
84        PackageContext::Project,
85        Some(&observer),
86    )
87    .graph;
88    (
89        graph,
90        projections
91            .into_inner()
92            .expect("module projection lock poisoned"),
93    )
94}
95
96/// Build a module graph that retains every reachable AST and honors unsaved
97/// buffer overrides.
98///
99/// This is the construction find-references and `harn graph` share: one
100/// resolution graph, one inverse index. `source_overrides` replace disk
101/// contents for the named files so an editor buffer wins over the file it
102/// just edited.
103pub fn build_for_reference_index(
104    files: &[PathBuf],
105    source_overrides: Option<&HashMap<PathBuf, String>>,
106) -> ModuleGraphBuild {
107    build_inner(
108        files,
109        ParsedSourceRetention::All,
110        source_overrides,
111        PackageContext::Project,
112        None,
113    )
114}
115
116#[cfg(test)]
117mod tests {
118    use super::*;
119
120    #[test]
121    fn projection_and_graph_share_the_parsed_source_snapshot() {
122        let dir = tempfile::tempdir().unwrap();
123        let paths = (0..12)
124            .map(|index| {
125                let path = dir.path().join(format!("module{index}.harn"));
126                std::fs::write(&path, "pub fn original() { 1 }\n").unwrap();
127                path
128            })
129            .collect::<Vec<_>>();
130        let observed = std::sync::atomic::AtomicUsize::new(0);
131        let (graph, projections) = build_with_module_projection(&paths, |path, source, program| {
132            observed.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
133            assert_eq!(program.unwrap().len(), 1);
134            // A change after this parse cannot change either projection.
135            std::fs::write(path, "pub fn replacement() { 2 }\n").unwrap();
136            source.to_owned()
137        });
138        assert_eq!(
139            observed.load(std::sync::atomic::Ordering::Relaxed),
140            paths.len()
141        );
142        assert_eq!(graph.module_paths().len(), paths.len());
143        assert_eq!(projections.len(), paths.len());
144        for path in &paths {
145            assert_eq!(
146                projections[&normalize_path(path)],
147                "pub fn original() { 1 }\n"
148            );
149            assert_eq!(
150                graph.exported_kind(path, "original"),
151                Some(super::super::DefKind::Function)
152            );
153            assert_eq!(graph.exported_kind(path, "replacement"), None);
154        }
155        let refreshed = build(&paths);
156        for path in &paths {
157            assert_eq!(refreshed.exported_kind(path, "original"), None);
158            assert_eq!(
159                refreshed.exported_kind(path, "replacement"),
160                Some(super::super::DefKind::Function)
161            );
162        }
163    }
164}