1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2use std::path::PathBuf;
3
4use diagnostics::{DiagnosticSink, SemanticResult};
5use syntax::ast::Expression;
6use syntax::program::{File, ModuleInfo, MutationInfo, UnusedInfo};
7
8use crate::cache::{
9 CompiledModule, compute_module_hash, get_dependency_module_hashes,
10 go_stdlib::{self, load_cached_go_module},
11 hash_module_sources, is_cache_disabled, prelude as prelude_cache, register_cached_module,
12 save_module_cache, try_load_cache,
13};
14use crate::checker::Checker;
15use crate::checker::infer::checks::check_interface_visibility;
16use crate::facts::Facts;
17use crate::lint;
18use crate::loader::Loader;
19use crate::module_graph::build_module_graph;
20use crate::pattern_analysis;
21use crate::prelude::parse_and_register_prelude;
22use crate::store::{ENTRY_MODULE_ID, Store};
23use stdlib::get_go_stdlib_typedef;
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
26pub enum CompilePhase {
27 #[default]
28 Check,
29 Emit,
30}
31
32#[derive(Debug, Clone, Default)]
33pub struct SemanticConfig {
34 pub run_lints: bool,
35 pub standalone_mode: bool,
36 pub load_siblings: bool,
37}
38
39pub struct AnalyzeInput<'a> {
40 pub config: SemanticConfig,
41 pub loader: &'a dyn Loader,
42 pub source: String,
43 pub filename: String,
44 pub ast: Vec<Expression>,
45 pub project_root: Option<PathBuf>,
46 pub compile_phase: CompilePhase,
47}
48
49pub fn analyze(input: AnalyzeInput) -> (SemanticResult, Facts) {
50 let mut store = Store::new();
51
52 store.init_entry_module();
53 store.store_entry_file(&input.filename, &input.source, input.ast);
54
55 let sink = DiagnosticSink::new();
56
57 if input.config.load_siblings {
58 for (filename, source) in input.loader.scan_folder(ENTRY_MODULE_ID) {
59 if filename == input.filename
60 || !filename.ends_with(".lis")
61 || filename.ends_with(".d.lis")
62 {
63 continue;
64 }
65 let file_id = store.new_file_id();
66 let result = syntax::build_ast(&source, file_id);
67 sink.extend_parse_errors(result.errors);
68 store.store_file(
69 ENTRY_MODULE_ID,
70 File::new(ENTRY_MODULE_ID, &filename, &source, result.ast, file_id),
71 );
72 }
73 }
74
75 let entry_module = store.entry_module_id().to_string();
76 let mut graph_result = build_module_graph(
77 &mut store,
78 Some(input.loader),
79 &entry_module,
80 &sink,
81 input.config.standalone_mode,
82 );
83
84 for cycle in &graph_result.cycles {
85 sink.push(diagnostics::module_graph::import_cycle(cycle));
86 }
87
88 let has_pre_check_errors = sink.has_errors();
89
90 let cache_disabled = is_cache_disabled();
91
92 let prelude_cache_hit = if cache_disabled {
93 false
94 } else if let Some(cached) = prelude_cache::try_load_prelude_cache() {
95 prelude_cache::register_cached_prelude(&mut store, cached);
96 true
97 } else {
98 false
99 };
100
101 if !prelude_cache_hit {
102 parse_and_register_prelude(&mut store, &sink);
103 }
104
105 let cache_enabled = input.project_root.is_some() && !cache_disabled;
106 let check_go_files = input.compile_phase == CompilePhase::Emit;
107
108 let (mut facts, coercions, resolutions, cached_modules, compiled_modules, ufcs_methods) = {
109 let mut checker = Checker::new(&mut store, &sink);
110 checker
111 .ufcs_methods
112 .extend(crate::prelude::compute_prelude_ufcs(checker.store));
113
114 let mut module_hashes: HashMap<String, u64> = HashMap::default();
115 let mut cached_modules: HashSet<String> = HashSet::default();
116 let mut compiled_modules: Vec<CompiledModule> = vec![];
117
118 let order = std::mem::take(&mut graph_result.order);
119 let edges = &graph_result.edges;
120
121 let go_cache = if cache_disabled {
122 None
123 } else {
124 go_stdlib::try_load_go_stdlib_cache()
125 };
126
127 for module_id in order {
128 if let Some(go_pkg) = module_id.strip_prefix("go:") {
129 if let Some(ref cache) = go_cache {
130 load_cached_go_module(checker.store, &module_id, cache);
132 if checker.store.is_visited(&module_id) {
133 continue;
134 }
135 }
136 if let Some(typedef) = get_go_stdlib_typedef(go_pkg) {
138 checker.parse_and_register_go_module(&module_id, typedef);
139 }
140 continue;
141 }
142
143 if checker.store.is_visited(&module_id) {
144 continue;
145 }
146
147 let files = graph_result.files.remove(&module_id).unwrap_or_default();
148 let source_hash = hash_module_sources(&files);
149
150 let dep_hashes = get_dependency_module_hashes(&module_id, edges, &module_hashes);
151 let module_hash = compute_module_hash(source_hash, &dep_hashes);
152 module_hashes.insert(module_id.clone(), module_hash);
153
154 let is_entry = module_id == ENTRY_MODULE_ID;
155
156 if cache_enabled
157 && !is_entry
158 && let Some(ref project_root) = input.project_root
159 && let Some(cached) = try_load_cache(
160 &module_id,
161 source_hash,
162 &dep_hashes,
163 project_root,
164 check_go_files,
165 )
166 {
167 checker
168 .ufcs_methods
169 .extend(cached.ufcs_methods.iter().cloned());
170 register_cached_module(checker.store, &module_id, cached);
171 cached_modules.insert(module_id.clone());
172 continue;
173 }
174
175 let prev_module_id = checker.cursor.module_id.clone();
176 checker.cursor.module_id = module_id.to_string();
177
178 checker.store.store_module(&module_id, files);
179 checker.register_module(&module_id);
180 checker.infer_module(&module_id);
181 check_interface_visibility(checker.store, &module_id, &sink);
182
183 checker.cursor.module_id = prev_module_id;
184
185 if cache_enabled && !is_entry {
186 compiled_modules.push(CompiledModule {
187 module_id: module_id.clone(),
188 source_hash,
189 dep_hashes,
190 });
191 }
192 }
193
194 if !cache_disabled {
196 let all_go_modules: Vec<String> = checker
197 .store
198 .modules
199 .keys()
200 .filter(|id| id.starts_with("go:"))
201 .cloned()
202 .collect();
203 let needs_save = !all_go_modules.is_empty()
204 && go_cache.as_ref().is_none_or(|c| {
205 all_go_modules.len() != c.modules.len()
206 || all_go_modules.iter().any(|id| !c.modules.contains_key(id))
207 });
208 if needs_save {
209 go_stdlib::save_go_stdlib_cache(checker.store, &all_go_modules);
210 }
211 }
212
213 if !cache_disabled && !prelude_cache_hit {
214 prelude_cache::save_prelude_cache(checker.store);
215 }
216
217 (
218 checker.facts,
219 checker.coercions,
220 checker.resolutions,
221 cached_modules,
222 compiled_modules,
223 checker.ufcs_methods,
224 )
225 };
226
227 let pattern_ctx = pattern_analysis::Context::new(&store, &facts.or_pattern_error_spans);
228 for module in store.modules.values() {
229 for file in module.files.values() {
230 for expression in &file.items {
231 pattern_analysis::check(expression, &pattern_ctx, &sink);
232 }
233 }
234 }
235 facts.pattern_issues = pattern_ctx.take_issues();
236
237 let errors = sink.take();
238
239 let unused = if input.config.run_lints && !has_pre_check_errors {
240 lint::lint_all_modules(&store, &facts, &sink)
241 } else {
242 UnusedInfo::default()
243 };
244
245 let mut mutations = MutationInfo::default();
246 for (&binding_id, b) in facts.bindings.iter() {
247 if b.mutated {
248 mutations.mark_binding_mutated(binding_id);
249 }
250 }
251
252 let lints = sink.take();
253
254 if cache_enabled && let Some(ref project_root) = input.project_root {
255 let has_errors = errors.iter().any(|e| e.is_error());
256 if !has_errors {
257 for compiled in compiled_modules {
258 let file_ids: HashSet<u32> = store
259 .get_module(&compiled.module_id)
260 .map(|m| m.file_ids().collect())
261 .unwrap_or_default();
262
263 let has_module_warnings = lints.iter().any(|lint| {
264 lint.file_id()
265 .map(|fid| file_ids.contains(&fid))
266 .unwrap_or(false)
267 });
268 if !has_module_warnings
269 && let Err(e) =
270 save_module_cache(&compiled, &store, project_root, &ufcs_methods)
271 {
272 eprintln!(
273 "warning: failed to write cache for {}: {e}",
274 compiled.module_id
275 );
276 }
277 }
278 }
279 }
280
281 let mut files = HashMap::default();
282 let mut definitions = HashMap::default();
283 let mut modules = HashMap::default();
284
285 for (mod_id, module) in store.modules {
286 let is_internal = module.is_internal();
287
288 definitions.extend(module.definitions);
289
290 if is_internal {
291 continue;
292 }
293
294 modules.insert(
295 mod_id,
296 ModuleInfo {
297 file_ids: module.files.keys().copied().collect(),
298 typedef_ids: module.typedefs.keys().copied().collect(),
299 id: module.id.clone(),
300 path: module.id,
301 },
302 );
303
304 files.extend(module.files);
305 files.extend(module.typedefs);
306 }
307
308 let result = SemanticResult {
309 files,
310 definitions,
311 modules,
312 errors,
313 lints,
314 entry_module_id: ENTRY_MODULE_ID.to_string(),
315 unused,
316 mutations,
317 coercions,
318 resolutions,
319 cached_modules,
320 ufcs_methods,
321 };
322
323 (result, facts)
324}