Skip to main content

crisp_resolve/
module.rs

1use crate::error::ResolveError;
2use crisp_ast::generics::{apply_implicit_generics, defined_type_names, prelude_type_set};
3use crisp_ast::item::SourceFile;
4use crisp_parser::Parser;
5use std::collections::BTreeMap;
6use std::fs;
7use std::path::{Path, PathBuf};
8
9#[derive(Debug, Clone)]
10pub struct ModuleNode {
11    pub path: PathBuf,
12    pub module_path: String,
13    pub ast: SourceFile,
14}
15
16#[derive(Debug, Clone)]
17pub struct ModuleGraph {
18    pub crate_root: PathBuf,
19    pub src_root: PathBuf,
20    pub modules: BTreeMap<String, ModuleNode>,
21}
22
23pub fn find_crate_root(start: &Path) -> Option<PathBuf> {
24    let mut dir = if start.is_dir() {
25        start.to_path_buf()
26    } else {
27        start.parent()?.to_path_buf()
28    };
29    loop {
30        if dir.join("crisp.toml").is_file() {
31            return Some(dir);
32        }
33        if !dir.pop() {
34            break;
35        }
36    }
37    None
38}
39
40pub fn load_module_graph(crate_root: &Path) -> Result<ModuleGraph, ResolveError> {
41    let src_root = crate_root.join("src");
42    if !src_root.is_dir() {
43        return Err(ResolveError::NoSrcDir {
44            root: crate_root.display().to_string(),
45        });
46    }
47
48    let mut modules = BTreeMap::new();
49    collect_crp_files(&src_root, &src_root, &mut modules)?;
50
51    if modules.is_empty() {
52        return Err(ResolveError::NoSrcDir {
53            root: crate_root.display().to_string(),
54        });
55    }
56
57    apply_free_type_binders(&mut modules)?;
58
59    Ok(ModuleGraph {
60        crate_root: crate_root.to_path_buf(),
61        src_root,
62        modules,
63    })
64}
65
66/// Unbound type names become item generics (#75). Explicit `<T>` that shadows a
67/// known type is E0049 (#78).
68fn apply_free_type_binders(modules: &mut BTreeMap<String, ModuleNode>) -> Result<(), ResolveError> {
69    let mut known = prelude_type_set();
70    for node in modules.values() {
71        known.extend(defined_type_names(&node.ast.items));
72    }
73    for node in modules.values_mut() {
74        apply_implicit_generics(&mut node.ast.items, &known).map_err(|shadow| {
75            ResolveError::GenericShadowsType {
76                name: shadow.name,
77                span: shadow.span,
78            }
79        })?;
80    }
81    Ok(())
82}
83
84fn collect_crp_files(
85    src_root: &Path,
86    dir: &Path,
87    out: &mut BTreeMap<String, ModuleNode>,
88) -> Result<(), ResolveError> {
89    for entry in fs::read_dir(dir).map_err(|e| ResolveError::Io {
90        path: dir.display().to_string(),
91        source: e,
92    })? {
93        let entry = entry.map_err(|e| ResolveError::Io {
94            path: dir.display().to_string(),
95            source: e,
96        })?;
97        let path = entry.path();
98        if path.is_dir() {
99            collect_crp_files(src_root, &path, out)?;
100            continue;
101        }
102        let ext = path.extension().and_then(|e| e.to_str());
103        let is_crpi = ext == Some("crpi");
104        if ext != Some("crp") && !is_crpi {
105            continue;
106        }
107        let rel = path.strip_prefix(src_root).map_err(|_| ResolveError::Io {
108            path: path.display().to_string(),
109            source: std::io::Error::new(std::io::ErrorKind::InvalidInput, "bad path"),
110        })?;
111        let rel_mod = rel
112            .with_extension("")
113            .to_string_lossy()
114            .replace(std::path::MAIN_SEPARATOR, ".");
115        // Sidecar decls must not shadow a `rust = true` crate of the same name (#116).
116        let module_path = if is_crpi {
117            format!("__extern.{rel_mod}")
118        } else {
119            rel_mod
120        };
121        let source = fs::read_to_string(&path).map_err(|e| ResolveError::Io {
122            path: path.display().to_string(),
123            source: e,
124        })?;
125        let mut parser = Parser::new(&source).map_err(|e| ResolveError::Parse {
126            path: path.display().to_string(),
127            message: e.primary_message(),
128            pos: e.byte_pos(),
129        })?;
130        let ast = parser.parse_file().map_err(|e| ResolveError::Parse {
131            path: path.display().to_string(),
132            message: e.primary_message(),
133            pos: e.byte_pos(),
134        })?;
135        out.insert(
136            module_path.clone(),
137            ModuleNode {
138                path,
139                module_path,
140                ast,
141            },
142        );
143    }
144    Ok(())
145}