1use crate::frontend::ast::{Expr, ExprKind, ImportItem, Span};
33use crate::backend::module_loader::ModuleLoader;
34use anyhow::{Result, Context};
35
36pub struct ModuleResolver {
41 module_loader: ModuleLoader,
43}
44
45impl ModuleResolver {
46 #[must_use]
53 pub fn new() -> Self {
54 Self {
55 module_loader: ModuleLoader::new(),
56 }
57 }
58
59 pub fn add_search_path<P: AsRef<std::path::Path>>(&mut self, path: P) {
65 self.module_loader.add_search_path(path);
66 }
67
68 pub fn resolve_imports(&mut self, ast: Expr) -> Result<Expr> {
88 self.resolve_expr(ast)
89 }
90
91 fn resolve_expr(&mut self, expr: Expr) -> Result<Expr> {
93 match expr.kind {
94 ExprKind::Import { ref path, ref items } => {
95 if self.is_file_import(path) {
97 let parsed_module = self.module_loader.load_module(path)
99 .with_context(|| format!("Failed to resolve import '{path}'"))?;
100
101 let resolved_module_ast = self.resolve_expr(parsed_module.ast)?;
103
104 let module_expr = Expr::new(
106 ExprKind::Module {
107 name: path.clone(),
108 body: Box::new(resolved_module_ast),
109 },
110 expr.span,
111 );
112
113 let use_statement = if items.iter().any(|item| matches!(item, ImportItem::Wildcard)) || items.is_empty() {
115 Expr::new(
117 ExprKind::Import {
118 path: path.clone(),
119 items: vec![ImportItem::Wildcard],
120 },
121 Span { start: 0, end: 0 },
122 )
123 } else {
124 self.create_use_statements(path, items)
126 };
127
128 Ok(Expr::new(
130 ExprKind::Block(vec![module_expr, use_statement]),
131 expr.span,
132 ))
133 } else {
134 Ok(expr)
136 }
137 }
138 ExprKind::Block(exprs) => {
139 let resolved_exprs: Result<Vec<_>> = exprs
141 .into_iter()
142 .map(|e| self.resolve_expr(e))
143 .collect();
144 Ok(Expr::new(ExprKind::Block(resolved_exprs?), expr.span))
145 }
146 ExprKind::Module { name, body } => {
147 let resolved_body = self.resolve_expr(*body)?;
149 Ok(Expr::new(
150 ExprKind::Module {
151 name,
152 body: Box::new(resolved_body),
153 },
154 expr.span,
155 ))
156 }
157 ExprKind::Function {
158 name,
159 type_params,
160 params,
161 body,
162 is_async,
163 return_type,
164 is_pub,
165 } => {
166 let resolved_body = self.resolve_expr(*body)?;
168 Ok(Expr::new(
169 ExprKind::Function {
170 name,
171 type_params,
172 params,
173 body: Box::new(resolved_body),
174 is_async,
175 return_type,
176 is_pub,
177 },
178 expr.span,
179 ))
180 }
181 ExprKind::If { condition, then_branch, else_branch } => {
182 let resolved_condition = self.resolve_expr(*condition)?;
183 let resolved_then = self.resolve_expr(*then_branch)?;
184 let resolved_else = else_branch.map(|e| self.resolve_expr(*e)).transpose()?;
185 Ok(Expr::new(
186 ExprKind::If {
187 condition: Box::new(resolved_condition),
188 then_branch: Box::new(resolved_then),
189 else_branch: resolved_else.map(Box::new),
190 },
191 expr.span,
192 ))
193 }
194 _ => Ok(expr),
197 }
198 }
199
200 fn is_file_import(&self, path: &str) -> bool {
202 !path.contains("::")
203 && !path.starts_with("std::")
204 && !path.starts_with("http")
205 && !path.is_empty()
206 }
207
208 fn create_use_statements(&self, module_path: &str, items: &[ImportItem]) -> Expr {
210 Expr::new(
213 ExprKind::Import {
214 path: module_path.to_string(), items: items.to_vec(),
216 },
217 Span { start: 0, end: 0 },
218 )
219 }
220
221 #[must_use]
223 pub fn stats(&self) -> crate::backend::module_loader::ModuleLoaderStats {
224 self.module_loader.stats()
225 }
226
227 pub fn clear_cache(&mut self) {
231 self.module_loader.clear_cache();
232 }
233}
234
235impl Default for ModuleResolver {
236 fn default() -> Self {
237 Self::new()
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use tempfile::TempDir;
245 use std::fs;
246 use crate::frontend::ast::Literal;
247
248 fn create_test_module(temp_dir: &TempDir, name: &str, content: &str) -> Result<()> {
249 let file_path = temp_dir.path().join(format!("{name}.ruchy"));
250 fs::write(file_path, content)?;
251 Ok(())
252 }
253
254 #[test]
255 fn test_module_resolver_creation() {
256 let resolver = ModuleResolver::new();
257 let stats = resolver.stats();
258 assert_eq!(stats.cached_modules, 0);
259 }
260
261 #[test]
262 fn test_add_search_path() {
263 let mut resolver = ModuleResolver::new();
264 resolver.add_search_path("/custom/path");
265 }
268
269 #[test]
270 fn test_is_file_import() {
271 let resolver = ModuleResolver::new();
272
273 assert!(resolver.is_file_import("math"));
275 assert!(resolver.is_file_import("utils"));
276 assert!(resolver.is_file_import("snake_case_module"));
277
278 assert!(!resolver.is_file_import("std::collections"));
280 assert!(!resolver.is_file_import("std::io::Read"));
281 assert!(!resolver.is_file_import("https://example.com/module.ruchy"));
282 assert!(!resolver.is_file_import("http://localhost/module.ruchy"));
283 assert!(!resolver.is_file_import(""));
284 }
285
286 #[test]
287 fn test_resolve_simple_file_import() -> Result<()> {
288 let temp_dir = TempDir::new()?;
289 let mut resolver = ModuleResolver::new();
290 resolver.add_search_path(temp_dir.path());
291
292 create_test_module(&temp_dir, "math", r"
294 pub fun add(a: i32, b: i32) -> i32 {
295 a + b
296 }
297 ")?;
298
299 let import_expr = Expr::new(
301 ExprKind::Import {
302 path: "math".to_string(),
303 items: vec![ImportItem::Wildcard],
304 },
305 Span { start: 0, end: 0 },
306 );
307
308 let resolved_expr = resolver.resolve_imports(import_expr)?;
310
311 match resolved_expr.kind {
313 ExprKind::Block(exprs) => {
314 assert_eq!(exprs.len(), 2);
315 match &exprs[0].kind {
317 ExprKind::Module { name, .. } => {
318 assert_eq!(name, "math");
319 }
320 _ => unreachable!("Expected first element to be Module, got {:?}", exprs[0].kind),
321 }
322 match &exprs[1].kind {
324 ExprKind::Import { path, items } => {
325 assert_eq!(path, "math");
326 assert_eq!(items.len(), 1);
327 assert!(matches!(items[0], ImportItem::Wildcard));
328 }
329 _ => unreachable!("Expected second element to be Import, got {:?}", exprs[1].kind),
330 }
331 }
332 _ => unreachable!("Expected Block expression, got {:?}", resolved_expr.kind),
333 }
334
335 Ok(())
336 }
337
338 #[test]
339 fn test_resolve_non_file_import() -> Result<()> {
340 let mut resolver = ModuleResolver::new();
341
342 let import_expr = Expr::new(
344 ExprKind::Import {
345 path: "std::collections".to_string(),
346 items: vec![ImportItem::Named("HashMap".to_string())],
347 },
348 Span { start: 0, end: 0 },
349 );
350
351 let resolved_expr = resolver.resolve_imports(import_expr)?;
353
354 match resolved_expr.kind {
355 ExprKind::Import { path, items } => {
356 assert_eq!(path, "std::collections");
357 assert_eq!(items.len(), 1);
358 }
359 _ => unreachable!("Expected Import expression to remain unchanged"),
360 }
361
362 Ok(())
363 }
364
365 #[test]
366 fn test_resolve_block_with_imports() -> Result<()> {
367 let temp_dir = TempDir::new()?;
368 let mut resolver = ModuleResolver::new();
369 resolver.add_search_path(temp_dir.path());
370
371 create_test_module(&temp_dir, "math", "pub fun add() {}")?;
372
373 let block_expr = Expr::new(
375 ExprKind::Block(vec![
376 Expr::new(
377 ExprKind::Import {
378 path: "math".to_string(),
379 items: vec![ImportItem::Wildcard],
380 },
381 Span { start: 0, end: 0 },
382 ),
383 Expr::new(
384 ExprKind::Import {
385 path: "std::io".to_string(),
386 items: vec![ImportItem::Named("Read".to_string())],
387 },
388 Span { start: 0, end: 0 },
389 ),
390 Expr::new(
391 ExprKind::Literal(Literal::Integer(42)),
392 Span { start: 0, end: 0 },
393 ),
394 ]),
395 Span { start: 0, end: 0 },
396 );
397
398 let resolved_block = resolver.resolve_imports(block_expr)?;
399
400 if let ExprKind::Block(exprs) = resolved_block.kind {
401 assert_eq!(exprs.len(), 3);
402
403 match &exprs[0].kind {
405 ExprKind::Block(inner_exprs) => {
406 assert_eq!(inner_exprs.len(), 2);
407 assert!(matches!(inner_exprs[0].kind, ExprKind::Module { .. }));
408 assert!(matches!(inner_exprs[1].kind, ExprKind::Import { .. }));
409 }
410 _ => unreachable!("Expected first element to be Block, got {:?}", exprs[0].kind),
411 }
412
413 assert!(matches!(exprs[1].kind, ExprKind::Import { .. }));
415
416 assert!(matches!(exprs[2].kind, ExprKind::Literal(Literal::Integer(42))));
418 } else {
419 unreachable!("Expected Block expression");
420 }
421
422 Ok(())
423 }
424
425 #[test]
426 fn test_stats_and_cache() -> Result<()> {
427 let temp_dir = TempDir::new()?;
428 let mut resolver = ModuleResolver::new();
429 resolver.add_search_path(temp_dir.path());
430
431 create_test_module(&temp_dir, "test", "pub fun test() {}")?;
432
433 let initial_stats = resolver.stats();
434 assert_eq!(initial_stats.files_loaded, 0);
435
436 let import_expr = Expr::new(
438 ExprKind::Import {
439 path: "test".to_string(),
440 items: vec![ImportItem::Wildcard],
441 },
442 Span { start: 0, end: 0 },
443 );
444
445 resolver.resolve_imports(import_expr)?;
446
447 let after_stats = resolver.stats();
448 assert_eq!(after_stats.files_loaded, 1);
449 assert_eq!(after_stats.cached_modules, 1);
450
451 resolver.clear_cache();
453 let cleared_stats = resolver.stats();
454 assert_eq!(cleared_stats.files_loaded, 0);
455 assert_eq!(cleared_stats.cached_modules, 0);
456
457 Ok(())
458 }
459}