Skip to main content

microcad_lang/resolve/
resolve_context.rs

1// Copyright © 2025-2026 The µcad authors <info@microcad.xyz>
2// SPDX-License-Identifier: AGPL-3.0-or-later
3
4//! Resolve Context
5
6use microcad_core::hash::HashSet;
7use microcad_lang_base::{
8    Diag, DiagHandler, DiagResult, Diagnostic, GetSourceLocInfoByHash, HashId, PushDiag,
9    ResourceLocation, SourceLocInfo, SrcReferrer, TreeDisplay, TreeState, WriteToFile,
10};
11
12use crate::{lower::ir, resolve::*, symbol::Symbol};
13
14/// Resolve Context
15pub struct ResolveContext {
16    /// Symbol table.
17    pub root: Symbol,
18    /// Source file cache.
19    pub(crate) sources: Sources,
20    /// Diagnostic handler.
21    pub diag: DiagHandler,
22}
23
24impl ResolveContext {
25    /// Load resolve and check a source file and referenced files.
26    pub fn create(
27        root: std::rc::Rc<ir::Source>,
28        search_paths: Vec<std::path::PathBuf>,
29        builtin: Option<Symbol>,
30        diag: DiagHandler,
31    ) -> ResolveResult<Self> {
32        let mut context = Self {
33            sources: Sources::load(root.clone(), search_paths)?,
34            diag,
35            root: Symbol::default(),
36        };
37        match context.load(builtin) {
38            Ok(()) => Ok(context),
39            Err(err) => {
40                context.error(&err.src_ref(), err)?;
41                Ok(context)
42            }
43        }
44    }
45
46    fn load(&mut self, builtin: Option<Symbol>) -> ResolveResult<()> {
47        self.symbolize()?;
48        log::trace!("Symbolized Context:\n{self:?}");
49        if let Some(builtin) = builtin {
50            log::trace!("Added builtin library {id}.", id = builtin.id());
51            self.root.add_symbol(builtin)?;
52        }
53        self.resolve()?;
54
55        Ok(())
56    }
57
58    pub(crate) fn symbolize(&mut self) -> ResolveResult<()> {
59        use crate::lower::SingleIdentifier;
60
61        let named_symbols = self
62            .sources
63            .clone()
64            .iter()
65            .map(|source| {
66                match (
67                    self.sources
68                        .generate_name_from_path(&source.to_file_path().unwrap()),
69                    source.symbolize(ir::Visibility::Public, self),
70                ) {
71                    (Ok(name), Ok(symbol)) => Ok((name, symbol)),
72                    (_, Err(err)) | (Err(err), _) => Err(err),
73                }
74            })
75            .collect::<ResolveResult<Vec<_>>>()?;
76        for (name, symbol) in named_symbols {
77            if let Some(id) = name.single_identifier() {
78                self.root.insert_symbol(id.clone(), symbol)?;
79            } else {
80                unreachable!("name is not an id")
81            }
82        }
83
84        Ok(())
85    }
86
87    fn resolve(&mut self) -> ResolveResult<()> {
88        // resolve std as first
89        if let Some(std) = self.root.get_child(&ir::Identifier::no_ref("std")) {
90            std.resolve(self)?;
91        }
92
93        // multi pass resolve
94        const MAX_PASSES: usize = 3;
95        let mut passes_needed = 0;
96        let mut resolved = false;
97        for _ in 0..MAX_PASSES {
98            self.root
99                .iter()
100                .filter(|child| child.is_resolvable())
101                .map(|child| child.resolve(self))
102                .collect::<Result<Vec<_>, _>>()?;
103            passes_needed += 1;
104            if !self.has_links() {
105                resolved = true;
106                break;
107            }
108            self.diag.clear()
109        }
110
111        if resolved {
112            log::info!("Resolve OK ({passes_needed} passes).");
113        } else {
114            log::info!("Resolve failed after {passes_needed} passes.");
115        }
116        log::debug!("Resolved symbol table:\n{self:?}");
117
118        Ok(())
119    }
120
121    fn has_links(&self) -> bool {
122        self.root
123            .iter()
124            .filter(|symbol| !symbol.is_deleted())
125            .any(|symbol| symbol.has_links())
126    }
127
128    /// Load file into source cache and symbolize it into a symbol.
129    pub fn symbolize_file(
130        &mut self,
131        visibility: ir::Visibility,
132        parent_path: impl AsRef<std::path::Path>,
133        id: &ir::Identifier,
134    ) -> ResolveResult<Symbol> {
135        let mut symbol = self
136            .sources
137            .load_mod_file(parent_path, id)?
138            .symbolize(visibility, self)?;
139        symbol.set_src_ref(id.src_ref());
140        Ok(symbol)
141    }
142}
143
144impl WriteToFile for ResolveContext {}
145
146impl PushDiag for ResolveContext {
147    fn push_diag(&mut self, diag: Diagnostic) -> DiagResult<()> {
148        self.diag.push_diag(diag)
149    }
150}
151
152impl GetSourceLocInfoByHash for ResolveContext {
153    fn get_source_loc_info_by_hash(&'_ self, hash: HashId) -> Option<SourceLocInfo<'_>> {
154        self.sources.get_source_loc_info_by_hash(hash)
155    }
156}
157
158impl Diag for ResolveContext {
159    fn fmt_diagnosis(&self, f: &mut dyn std::fmt::Write) -> std::fmt::Result {
160        self.diag.pretty_print(f, self)
161    }
162
163    fn warning_count(&self) -> u32 {
164        self.diag.warning_count()
165    }
166
167    fn error_count(&self) -> u32 {
168        self.diag.error_count()
169    }
170
171    fn error_lines(&self) -> HashSet<u32> {
172        self.diag.error_lines()
173    }
174
175    fn warning_lines(&self) -> HashSet<u32> {
176        self.diag.warning_lines()
177    }
178}
179
180impl GetSourceByHash for ResolveContext {
181    fn get_by_hash(&self, hash: u64) -> ResolveResult<std::rc::Rc<ir::Source>> {
182        self.sources.get_by_hash(hash)
183    }
184}
185
186impl std::fmt::Debug for ResolveContext {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        writeln!(f, "Sources:\n")?;
189        write!(f, "{:?}", &self.sources)?;
190        writeln!(f, "\nSymbols:\n")?;
191        self.root.tree_print(f, TreeState::new_debug(1))?;
192        let err_count = self.diag.error_count();
193        if err_count == 0 {
194            writeln!(f, "No errors.")?;
195        } else {
196            writeln!(f, "\n{err_count} error(s):\n")?;
197            self.diag.pretty_print(f, &self.sources)?;
198        }
199
200        Ok(())
201    }
202}
203
204impl std::fmt::Display for ResolveContext {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        writeln!(f, "Resolved symbols:")?;
207        self.root.tree_print(f, TreeState::new_display())?;
208
209        if self.has_errors() {
210            writeln!(
211                f,
212                "{diag}{err} error(s) and {warn} warning(s) so far.",
213                err = self.error_count(),
214                warn = self.warning_count(),
215                diag = self.diagnosis()
216            )?;
217        } else {
218            writeln!(f, "No errors so far.")?;
219        }
220        Ok(())
221    }
222}