Skip to main content

lust/modules/
mod.rs

1use crate::{
2    ast::{FunctionDef, Item, ItemKind, UseTree, Visibility},
3    error::{LustError, Result},
4    lexer::Lexer,
5    parser::Parser,
6    Span,
7};
8use alloc::{format, string::String, vec, vec::Vec};
9use hashbrown::{HashMap, HashSet};
10#[cfg(feature = "std")]
11use std::{
12    fs,
13    path::{Path, PathBuf},
14};
15#[derive(Debug, Clone, Default)]
16pub struct ModuleImports {
17    pub function_aliases: HashMap<String, String>,
18    pub module_aliases: HashMap<String, String>,
19    pub type_aliases: HashMap<String, String>,
20}
21
22#[derive(Debug, Clone, Default)]
23pub struct ModuleExports {
24    pub functions: HashMap<String, String>,
25    pub types: HashMap<String, String>,
26}
27
28pub mod embedded;
29
30#[derive(Debug, Clone)]
31pub struct LoadedModule {
32    pub path: String,
33    pub items: Vec<Item>,
34    pub imports: ModuleImports,
35    pub exports: ModuleExports,
36    pub init_function: Option<String>,
37    #[cfg(feature = "std")]
38    pub source_path: PathBuf,
39}
40
41#[derive(Debug, Clone)]
42pub struct Program {
43    pub modules: Vec<LoadedModule>,
44    pub entry_module: String,
45}
46
47pub use embedded::{build_directory_map, load_program_from_embedded, EmbeddedModule};
48#[derive(Clone, Copy, Debug, Default)]
49struct ImportResolution {
50    import_value: bool,
51    import_type: bool,
52}
53
54impl ImportResolution {
55    fn both() -> Self {
56        Self {
57            import_value: true,
58            import_type: true,
59        }
60    }
61}
62
63#[cfg(feature = "std")]
64pub struct ModuleLoader {
65    base_dir: PathBuf,
66    cache: HashMap<String, LoadedModule>,
67    visited: HashSet<String>,
68    source_overrides: HashMap<PathBuf, String>,
69    module_roots: HashMap<String, Vec<ModuleRoot>>,
70}
71
72#[cfg(feature = "std")]
73#[derive(Debug, Clone)]
74struct ModuleRoot {
75    base: PathBuf,
76    root_module: Option<PathBuf>,
77}
78
79#[cfg(feature = "std")]
80impl ModuleLoader {
81    pub fn new(base_dir: impl Into<PathBuf>) -> Self {
82        Self {
83            base_dir: base_dir.into(),
84            cache: HashMap::new(),
85            visited: HashSet::new(),
86            source_overrides: HashMap::new(),
87            module_roots: HashMap::new(),
88        }
89    }
90
91    pub fn set_source_overrides(&mut self, overrides: HashMap<PathBuf, String>) {
92        self.source_overrides = overrides;
93    }
94
95    pub fn set_source_override<P: Into<PathBuf>, S: Into<String>>(&mut self, path: P, source: S) {
96        self.source_overrides.insert(path.into(), source.into());
97    }
98
99    pub fn clear_source_overrides(&mut self) {
100        self.source_overrides.clear();
101    }
102
103    pub fn add_module_root(
104        &mut self,
105        prefix: impl Into<String>,
106        root: impl Into<PathBuf>,
107        root_module: Option<PathBuf>,
108    ) {
109        self.module_roots
110            .entry(prefix.into())
111            .or_default()
112            .push(ModuleRoot {
113                base: root.into(),
114                root_module,
115            });
116    }
117
118    pub fn load_program_from_entry(&mut self, entry_file: &str) -> Result<Program> {
119        let entry_path = Path::new(entry_file);
120        let entry_dir = entry_path.parent().unwrap_or_else(|| Path::new("."));
121        self.base_dir = entry_dir.to_path_buf();
122        let entry_module = Self::module_path_for_file(entry_path);
123        let mut order: Vec<String> = Vec::new();
124        let mut stack: HashSet<String> = HashSet::new();
125        self.load_module_recursive(&entry_module, &mut order, &mut stack, true)?;
126        let modules = order
127            .into_iter()
128            .filter_map(|m| self.cache.get(&m).cloned())
129            .collect::<Vec<_>>();
130        Ok(Program {
131            modules,
132            entry_module,
133        })
134    }
135
136    fn load_module_recursive(
137        &mut self,
138        module_path: &str,
139        order: &mut Vec<String>,
140        stack: &mut HashSet<String>,
141        is_entry: bool,
142    ) -> Result<()> {
143        if self.visited.contains(module_path) {
144            return Ok(());
145        }
146
147        if !stack.insert(module_path.to_string()) {
148            return Ok(());
149        }
150
151        let mut loaded = self.load_single_module(module_path, is_entry)?;
152        self.cache.insert(module_path.to_string(), loaded.clone());
153        let deps = self.collect_dependencies(&loaded.items);
154        for dep in deps {
155            self.load_module_recursive(&dep, order, stack, false)?;
156        }
157
158        self.finalize_module(&mut loaded)?;
159        self.cache.insert(module_path.to_string(), loaded.clone());
160        self.visited.insert(module_path.to_string());
161        order.push(module_path.to_string());
162        stack.remove(module_path);
163        Ok(())
164    }
165
166    fn load_single_module(&self, module_path: &str, is_entry: bool) -> Result<LoadedModule> {
167        let file = self.file_for_module_path(module_path);
168        let source = if let Some(src) = self.source_overrides.get(&file) {
169            src.clone()
170        } else {
171            match fs::read_to_string(&file) {
172                Ok(src) => src,
173                Err(e) => {
174                    // For non-entry modules, allow missing files and create stub
175                    if !is_entry && e.kind() == std::io::ErrorKind::NotFound {
176                        #[cfg(feature = "std")]
177                        eprintln!(
178                            "[WARNING] Module '{}' not found, but present in code",
179                            module_path
180                        );
181                        //return Ok(self.create_nil_stub_module(module_path));
182                    }
183                    return Err(LustError::Unknown(format!(
184                        "Failed to read module '{}': {}",
185                        file.display(),
186                        e
187                    )));
188                }
189            }
190        };
191        let mut lexer = Lexer::new(&source);
192        let tokens = lexer
193            .tokenize()
194            .map_err(|err| Self::attach_module_to_error(err, module_path))?;
195        let mut parser = Parser::new(tokens);
196        let mut items = parser
197            .parse()
198            .map_err(|err| Self::attach_module_to_error(err, module_path))?;
199        let mut imports = ModuleImports::default();
200        let mut exports = ModuleExports::default();
201        let mut new_items: Vec<Item> = Vec::new();
202        let mut init_function: Option<String> = None;
203        let mut pending_init_stmts: Vec<crate::ast::Stmt> = Vec::new();
204        let mut pending_init_span: Option<Span> = None;
205        for item in items.drain(..) {
206            match &item.kind {
207                ItemKind::Function(func) => {
208                    let mut f = func.clone();
209                    if !f.is_method && !f.name.contains(':') && !f.name.contains('.') {
210                        let fq = format!("{}.{}", module_path, f.name);
211                        imports.function_aliases.insert(f.name.clone(), fq.clone());
212                        f.name = fq.clone();
213                        if matches!(f.visibility, Visibility::Public) {
214                            exports
215                                .functions
216                                .insert(self.simple_name(&f.name).to_string(), f.name.clone());
217                        }
218                    } else {
219                        if matches!(f.visibility, Visibility::Public) {
220                            exports
221                                .functions
222                                .insert(self.simple_name(&f.name).to_string(), f.name.clone());
223                        }
224                    }
225
226                    new_items.push(Item::new(ItemKind::Function(f), item.span));
227                }
228
229                ItemKind::Struct(s) => {
230                    if matches!(s.visibility, Visibility::Public) {
231                        exports
232                            .types
233                            .insert(s.name.clone(), format!("{}.{}", module_path, s.name));
234                    }
235
236                    new_items.push(item);
237                }
238
239                ItemKind::Enum(e) => {
240                    if matches!(e.visibility, Visibility::Public) {
241                        exports
242                            .types
243                            .insert(e.name.clone(), format!("{}.{}", module_path, e.name));
244                    }
245
246                    new_items.push(item);
247                }
248
249                ItemKind::Trait(t) => {
250                    if matches!(t.visibility, Visibility::Public) {
251                        exports
252                            .types
253                            .insert(t.name.clone(), format!("{}.{}", module_path, t.name));
254                    }
255
256                    new_items.push(item);
257                }
258
259                ItemKind::TypeAlias { name, .. } => {
260                    exports
261                        .types
262                        .insert(name.clone(), format!("{}.{}", module_path, name));
263                    new_items.push(item);
264                }
265
266                ItemKind::Script(stmts) => {
267                    if is_entry {
268                        new_items.push(Item::new(ItemKind::Script(stmts.clone()), item.span));
269                    } else {
270                        if pending_init_span.is_none() {
271                            pending_init_span = Some(item.span);
272                        }
273                        pending_init_stmts.extend(stmts.iter().cloned());
274                    }
275                }
276
277                ItemKind::Extern {
278                    abi,
279                    items: extern_items,
280                } => {
281                    let mut rewritten = Vec::new();
282                    for extern_item in extern_items {
283                        match extern_item {
284                            crate::ast::ExternItem::Function {
285                                name,
286                                params,
287                                return_type,
288                            } => {
289                                let mut new_name = name.clone();
290                                if let Some((head, tail)) = new_name.split_once(':') {
291                                    let qualified_head =
292                                        if head.contains('.') || head.contains("::") {
293                                            head.to_string()
294                                        } else {
295                                            format!("{}.{}", module_path, head)
296                                        };
297                                    new_name = format!("{}:{}", qualified_head, tail);
298                                } else if !new_name.contains('.') {
299                                    new_name = format!("{}.{}", module_path, new_name);
300                                }
301
302                                exports.functions.insert(
303                                    self.simple_name(&new_name).to_string(),
304                                    new_name.clone(),
305                                );
306                                imports.function_aliases.insert(
307                                    self.simple_name(&new_name).to_string(),
308                                    new_name.clone(),
309                                );
310
311                                rewritten.push(crate::ast::ExternItem::Function {
312                                    name: new_name,
313                                    params: params.clone(),
314                                    return_type: return_type.clone(),
315                                });
316                            }
317
318                            crate::ast::ExternItem::Const { name, ty } => {
319                                let qualified = if name.contains('.') {
320                                    name.clone()
321                                } else {
322                                    format!("{}.{}", module_path, name)
323                                };
324                                exports.functions.insert(
325                                    self.simple_name(&qualified).to_string(),
326                                    qualified.clone(),
327                                );
328                                imports.function_aliases.insert(
329                                    self.simple_name(&qualified).to_string(),
330                                    qualified.clone(),
331                                );
332                                rewritten.push(crate::ast::ExternItem::Const {
333                                    name: qualified,
334                                    ty: ty.clone(),
335                                });
336                            }
337
338                            crate::ast::ExternItem::Struct(def) => {
339                                let mut def = def.clone();
340                                if !def.name.contains('.') && !def.name.contains("::") {
341                                    def.name = format!("{}.{}", module_path, def.name);
342                                }
343                                exports.types.insert(
344                                    self.simple_name(&def.name).to_string(),
345                                    def.name.clone(),
346                                );
347                                rewritten.push(crate::ast::ExternItem::Struct(def));
348                            }
349
350                            crate::ast::ExternItem::Enum(def) => {
351                                let mut def = def.clone();
352                                if !def.name.contains('.') && !def.name.contains("::") {
353                                    def.name = format!("{}.{}", module_path, def.name);
354                                }
355                                exports.types.insert(
356                                    self.simple_name(&def.name).to_string(),
357                                    def.name.clone(),
358                                );
359                                rewritten.push(crate::ast::ExternItem::Enum(def));
360                            }
361                        }
362                    }
363                    new_items.push(Item::new(
364                        ItemKind::Extern {
365                            abi: abi.clone(),
366                            items: rewritten,
367                        },
368                        item.span,
369                    ));
370                }
371
372                _ => {
373                    new_items.push(item);
374                }
375            }
376        }
377
378        if !is_entry && !pending_init_stmts.is_empty() {
379            let init_name = format!("__init@{}", module_path);
380            let func = FunctionDef {
381                name: init_name.clone(),
382                type_params: vec![],
383                trait_bounds: vec![],
384                params: vec![],
385                return_type: None,
386                body: pending_init_stmts,
387                is_method: false,
388                visibility: Visibility::Private,
389            };
390            let span = pending_init_span.unwrap_or_else(Span::dummy);
391            // Place module init first so the compiler can observe module-level locals
392            // (e.g. transpiler prelude helpers) before compiling functions that reference them.
393            new_items.insert(0, Item::new(ItemKind::Function(func), span));
394            init_function = Some(init_name);
395        }
396
397        Ok(LoadedModule {
398            path: module_path.to_string(),
399            items: new_items,
400            imports,
401            exports,
402            init_function,
403            source_path: file,
404        })
405    }
406
407    fn collect_dependencies(&self, items: &[Item]) -> Vec<String> {
408        let mut deps = HashSet::new();
409        for item in items {
410            match &item.kind {
411                ItemKind::Use { public: _, tree } => {
412                    self.collect_deps_from_use(tree, &mut deps);
413                }
414                ItemKind::Script(stmts) => {
415                    for stmt in stmts {
416                        self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
417                    }
418                }
419                ItemKind::Function(func) => {
420                    for stmt in &func.body {
421                        self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
422                    }
423                }
424                ItemKind::Const { value, .. } | ItemKind::Static { value, .. } => {
425                    self.collect_deps_from_lua_require_expr(value, &mut deps);
426                }
427                ItemKind::Impl(impl_block) => {
428                    for method in &impl_block.methods {
429                        for stmt in &method.body {
430                            self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
431                        }
432                    }
433                }
434                ItemKind::Trait(trait_def) => {
435                    for method in &trait_def.methods {
436                        if let Some(default_impl) = &method.default_impl {
437                            for stmt in default_impl {
438                                self.collect_deps_from_lua_require_stmt(stmt, &mut deps);
439                            }
440                        }
441                    }
442                }
443                _ => {}
444            }
445        }
446
447        deps.into_iter().collect()
448    }
449
450    fn collect_deps_from_lua_require_stmt(
451        &self,
452        stmt: &crate::ast::Stmt,
453        deps: &mut HashSet<String>,
454    ) {
455        use crate::ast::StmtKind;
456        match &stmt.kind {
457            StmtKind::Local { initializer, .. } => {
458                if let Some(values) = initializer {
459                    for expr in values {
460                        self.collect_deps_from_lua_require_expr(expr, deps);
461                    }
462                }
463            }
464            StmtKind::Assign { targets, values } => {
465                for expr in targets {
466                    self.collect_deps_from_lua_require_expr(expr, deps);
467                }
468                for expr in values {
469                    self.collect_deps_from_lua_require_expr(expr, deps);
470                }
471            }
472            StmtKind::CompoundAssign { target, value, .. } => {
473                self.collect_deps_from_lua_require_expr(target, deps);
474                self.collect_deps_from_lua_require_expr(value, deps);
475            }
476            StmtKind::Expr(expr) => self.collect_deps_from_lua_require_expr(expr, deps),
477            StmtKind::If {
478                condition,
479                then_block,
480                elseif_branches,
481                else_block,
482            } => {
483                self.collect_deps_from_lua_require_expr(condition, deps);
484                for stmt in then_block {
485                    self.collect_deps_from_lua_require_stmt(stmt, deps);
486                }
487                for (cond, block) in elseif_branches {
488                    self.collect_deps_from_lua_require_expr(cond, deps);
489                    for stmt in block {
490                        self.collect_deps_from_lua_require_stmt(stmt, deps);
491                    }
492                }
493                if let Some(block) = else_block {
494                    for stmt in block {
495                        self.collect_deps_from_lua_require_stmt(stmt, deps);
496                    }
497                }
498            }
499            StmtKind::While { condition, body } => {
500                self.collect_deps_from_lua_require_expr(condition, deps);
501                for stmt in body {
502                    self.collect_deps_from_lua_require_stmt(stmt, deps);
503                }
504            }
505            StmtKind::ForNumeric {
506                start,
507                end,
508                step,
509                body,
510                ..
511            } => {
512                self.collect_deps_from_lua_require_expr(start, deps);
513                self.collect_deps_from_lua_require_expr(end, deps);
514                if let Some(step) = step {
515                    self.collect_deps_from_lua_require_expr(step, deps);
516                }
517                for stmt in body {
518                    self.collect_deps_from_lua_require_stmt(stmt, deps);
519                }
520            }
521            StmtKind::ForIn { iterator, body, .. } => {
522                self.collect_deps_from_lua_require_expr(iterator, deps);
523                for stmt in body {
524                    self.collect_deps_from_lua_require_stmt(stmt, deps);
525                }
526            }
527            StmtKind::Return(values) => {
528                for expr in values {
529                    self.collect_deps_from_lua_require_expr(expr, deps);
530                }
531            }
532            StmtKind::Block(stmts) => {
533                for stmt in stmts {
534                    self.collect_deps_from_lua_require_stmt(stmt, deps);
535                }
536            }
537            StmtKind::Break | StmtKind::Continue => {}
538        }
539    }
540
541    fn collect_deps_from_lua_require_expr(
542        &self,
543        expr: &crate::ast::Expr,
544        deps: &mut HashSet<String>,
545    ) {
546        use crate::ast::{ExprKind, Literal};
547        match &expr.kind {
548            ExprKind::Call { callee, args } => {
549                if self.is_lua_require_callee(callee) {
550                    if let Some(name) = args
551                        .get(0)
552                        .and_then(|arg| self.extract_lua_require_name(arg))
553                    {
554                        if !Self::is_lua_builtin_module_name(&name) {
555                            // `lua.require()` calls originate from transpiled Lua stubs. Unlike
556                            // Lust `use` imports, these should only pull in modules that we can
557                            // actually locate in the current module roots (extern stubs, on-disk
558                            // modules, or source overrides). This prevents optional Lua requires
559                            // (e.g. `ssl.https`) from becoming hard compile-time dependencies.
560                            let file = self.file_for_module_path(&name);
561                            if self.module_source_known(&name, &file) {
562                                deps.insert(name);
563                            }
564                        }
565                    }
566                }
567                self.collect_deps_from_lua_require_expr(callee, deps);
568                for arg in args {
569                    self.collect_deps_from_lua_require_expr(arg, deps);
570                }
571            }
572            ExprKind::MethodCall { receiver, args, .. } => {
573                self.collect_deps_from_lua_require_expr(receiver, deps);
574                for arg in args {
575                    self.collect_deps_from_lua_require_expr(arg, deps);
576                }
577            }
578            ExprKind::Binary { left, right, .. } => {
579                self.collect_deps_from_lua_require_expr(left, deps);
580                self.collect_deps_from_lua_require_expr(right, deps);
581            }
582            ExprKind::Unary { operand, .. } => {
583                self.collect_deps_from_lua_require_expr(operand, deps)
584            }
585            ExprKind::FieldAccess { object, .. } => {
586                self.collect_deps_from_lua_require_expr(object, deps)
587            }
588            ExprKind::Index { object, index } => {
589                self.collect_deps_from_lua_require_expr(object, deps);
590                self.collect_deps_from_lua_require_expr(index, deps);
591            }
592            ExprKind::Array(elements) | ExprKind::Tuple(elements) => {
593                for element in elements {
594                    self.collect_deps_from_lua_require_expr(element, deps);
595                }
596            }
597            ExprKind::Map(entries) => {
598                for (k, v) in entries {
599                    self.collect_deps_from_lua_require_expr(k, deps);
600                    self.collect_deps_from_lua_require_expr(v, deps);
601                }
602            }
603            ExprKind::StructLiteral { fields, .. } => {
604                for field in fields {
605                    self.collect_deps_from_lua_require_expr(&field.value, deps);
606                }
607            }
608            ExprKind::EnumConstructor { args, .. } => {
609                for arg in args {
610                    self.collect_deps_from_lua_require_expr(arg, deps);
611                }
612            }
613            ExprKind::Lambda { body, .. } => self.collect_deps_from_lua_require_expr(body, deps),
614            ExprKind::Paren(inner) => self.collect_deps_from_lua_require_expr(inner, deps),
615            ExprKind::Cast { expr, .. } => self.collect_deps_from_lua_require_expr(expr, deps),
616            ExprKind::TypeCheck { expr, .. } => self.collect_deps_from_lua_require_expr(expr, deps),
617            ExprKind::IsPattern { expr, .. } => self.collect_deps_from_lua_require_expr(expr, deps),
618            ExprKind::If {
619                condition,
620                then_branch,
621                else_branch,
622            } => {
623                self.collect_deps_from_lua_require_expr(condition, deps);
624                self.collect_deps_from_lua_require_expr(then_branch, deps);
625                if let Some(other) = else_branch {
626                    self.collect_deps_from_lua_require_expr(other, deps);
627                }
628            }
629            ExprKind::Block(stmts) => {
630                for stmt in stmts {
631                    self.collect_deps_from_lua_require_stmt(stmt, deps);
632                }
633            }
634            ExprKind::Return(values) => {
635                for value in values {
636                    self.collect_deps_from_lua_require_expr(value, deps);
637                }
638            }
639            ExprKind::Range { start, end, .. } => {
640                self.collect_deps_from_lua_require_expr(start, deps);
641                self.collect_deps_from_lua_require_expr(end, deps);
642            }
643            ExprKind::Literal(Literal::String(_))
644            | ExprKind::Literal(_)
645            | ExprKind::Identifier(_) => {}
646        }
647    }
648
649    fn is_lua_builtin_module_name(name: &str) -> bool {
650        matches!(
651            name,
652            "math" | "table" | "string" | "io" | "os" | "package" | "coroutine" | "debug" | "utf8"
653        )
654    }
655
656    fn is_lua_require_callee(&self, callee: &crate::ast::Expr) -> bool {
657        use crate::ast::ExprKind;
658        match &callee.kind {
659            ExprKind::Identifier(name) => name == "require",
660            ExprKind::FieldAccess { object, field } => {
661                field == "require"
662                    && matches!(&object.kind, ExprKind::Identifier(name) if name == "lua")
663            }
664            _ => false,
665        }
666    }
667
668    fn extract_lua_require_name(&self, expr: &crate::ast::Expr) -> Option<String> {
669        use crate::ast::{ExprKind, Literal};
670        match &expr.kind {
671            ExprKind::Literal(Literal::String(s)) => Some(s.clone()),
672            ExprKind::Call { callee, args } if self.is_lua_to_value_callee(callee) => {
673                args.get(0).and_then(|arg| match &arg.kind {
674                    ExprKind::Literal(Literal::String(s)) => Some(s.clone()),
675                    _ => None,
676                })
677            }
678            _ => None,
679        }
680    }
681
682    fn is_lua_to_value_callee(&self, callee: &crate::ast::Expr) -> bool {
683        use crate::ast::ExprKind;
684        matches!(
685            &callee.kind,
686            ExprKind::FieldAccess { object, field }
687                if field == "to_value"
688                    && matches!(&object.kind, ExprKind::Identifier(name) if name == "lua")
689        )
690    }
691
692    fn finalize_module(&mut self, module: &mut LoadedModule) -> Result<()> {
693        for item in &module.items {
694            if let ItemKind::Use { tree, .. } = &item.kind {
695                self.process_use_tree(tree, &mut module.imports)?;
696            }
697        }
698
699        for item in &module.items {
700            if let ItemKind::Use { public: true, tree } = &item.kind {
701                self.apply_reexport(tree, &mut module.exports)?;
702            }
703        }
704
705        module
706            .imports
707            .module_aliases
708            .entry(self.simple_tail(&module.path).to_string())
709            .or_insert_with(|| module.path.clone());
710        Ok(())
711    }
712
713    fn collect_deps_from_use(&self, tree: &UseTree, deps: &mut HashSet<String>) {
714        match tree {
715            UseTree::Path {
716                path,
717                alias: _,
718                import_module: _,
719            } => {
720                let full = path.join(".");
721                let full_file = self.file_for_module_path(&full);
722                if self.module_source_known(&full, &full_file) {
723                    deps.insert(full);
724                } else if path.len() > 1 {
725                    deps.insert(path[..path.len() - 1].join("."));
726                }
727            }
728
729            UseTree::Group { prefix, items } => {
730                let module = prefix.join(".");
731                if !module.is_empty() {
732                    deps.insert(module);
733                }
734
735                for item in items {
736                    if item.path.len() > 1 {
737                        let mut combined: Vec<String> = prefix.clone();
738                        combined.extend(item.path[..item.path.len() - 1].iter().cloned());
739                        let module_path = combined.join(".");
740                        if !module_path.is_empty() {
741                            deps.insert(module_path);
742                        }
743                    }
744                }
745            }
746
747            UseTree::Glob { prefix } => {
748                deps.insert(prefix.join("."));
749            }
750        }
751    }
752
753    fn process_use_tree(&self, tree: &UseTree, imports: &mut ModuleImports) -> Result<()> {
754        match tree {
755            UseTree::Path { path, alias, .. } => {
756                let full = path.join(".");
757                let full_file = self.file_for_module_path(&full);
758                if self.module_source_known(&full, &full_file) {
759                    let alias_name = alias
760                        .clone()
761                        .unwrap_or_else(|| path.last().unwrap().clone());
762                    imports.module_aliases.insert(alias_name, full);
763                } else if path.len() > 1 {
764                    let module = path[..path.len() - 1].join(".");
765                    let item = path.last().unwrap().clone();
766                    let alias_name = alias.clone().unwrap_or_else(|| item.clone());
767                    let classification = self.classify_import_target(&module, &item);
768                    let fq = format!("{}.{}", module, item);
769                    if classification.import_value {
770                        imports
771                            .function_aliases
772                            .insert(alias_name.clone(), fq.clone());
773                    }
774
775                    if classification.import_type {
776                        imports.type_aliases.insert(alias_name, fq);
777                    }
778                }
779            }
780
781            UseTree::Group { prefix, items } => {
782                for item in items {
783                    if item.path.is_empty() {
784                        continue;
785                    }
786
787                    let alias_name = item
788                        .alias
789                        .clone()
790                        .unwrap_or_else(|| item.path.last().unwrap().clone());
791                    let mut full_segments = prefix.clone();
792                    full_segments.extend(item.path.clone());
793                    let full = full_segments.join(".");
794                    let full_file = self.file_for_module_path(&full);
795                    if self.module_source_known(&full, &full_file) {
796                        imports.module_aliases.insert(alias_name, full);
797                        continue;
798                    }
799
800                    let mut module_segments = full_segments.clone();
801                    let item_name = module_segments.pop().unwrap();
802                    let module_path = module_segments.join(".");
803                    let fq_name = if module_path.is_empty() {
804                        item_name.clone()
805                    } else {
806                        format!("{}.{}", module_path, item_name)
807                    };
808                    let classification = self.classify_import_target(&module_path, &item_name);
809                    if classification.import_value {
810                        imports
811                            .function_aliases
812                            .insert(alias_name.clone(), fq_name.clone());
813                    }
814
815                    if classification.import_type {
816                        imports.type_aliases.insert(alias_name.clone(), fq_name);
817                    }
818                }
819            }
820
821            UseTree::Glob { prefix } => {
822                let module = prefix.join(".");
823                if let Some(loaded) = self.cache.get(&module) {
824                    for (name, fq) in &loaded.exports.functions {
825                        imports.function_aliases.insert(name.clone(), fq.clone());
826                    }
827
828                    for (name, fq) in &loaded.exports.types {
829                        imports.type_aliases.insert(name.clone(), fq.clone());
830                    }
831                }
832
833                let alias_name = prefix.last().cloned().unwrap_or_else(|| module.clone());
834                if !module.is_empty() {
835                    imports.module_aliases.insert(alias_name, module);
836                }
837            }
838        }
839
840        Ok(())
841    }
842
843    fn attach_module_to_error(error: LustError, module_path: &str) -> LustError {
844        match error {
845            LustError::LexerError {
846                line,
847                column,
848                message,
849                module,
850            } => LustError::LexerError {
851                line,
852                column,
853                message,
854                module: module.or_else(|| Some(module_path.to_string())),
855            },
856            LustError::ParserError {
857                line,
858                column,
859                message,
860                module,
861            } => LustError::ParserError {
862                line,
863                column,
864                message,
865                module: module.or_else(|| Some(module_path.to_string())),
866            },
867            LustError::CompileErrorWithSpan {
868                message,
869                line,
870                column,
871                module,
872            } => LustError::CompileErrorWithSpan {
873                message,
874                line,
875                column,
876                module: module.or_else(|| Some(module_path.to_string())),
877            },
878            other => other,
879        }
880    }
881
882    fn apply_reexport(&self, tree: &UseTree, exports: &mut ModuleExports) -> Result<()> {
883        match tree {
884            UseTree::Path { path, alias, .. } => {
885                if path.len() == 1 {
886                    return Ok(());
887                }
888
889                let module = path[..path.len() - 1].join(".");
890                let item = path.last().unwrap().clone();
891                let alias_name = alias.clone().unwrap_or_else(|| item.clone());
892                let fq = format!("{}.{}", module, item);
893                let classification = self.classify_import_target(&module, &item);
894                if classification.import_type {
895                    exports.types.insert(alias_name.clone(), fq.clone());
896                }
897
898                if classification.import_value {
899                    exports.functions.insert(alias_name, fq);
900                }
901
902                Ok(())
903            }
904
905            UseTree::Group { prefix, items } => {
906                for item in items {
907                    if item.path.is_empty() {
908                        continue;
909                    }
910
911                    let alias_name = item
912                        .alias
913                        .clone()
914                        .unwrap_or_else(|| item.path.last().unwrap().clone());
915                    let mut full_segments = prefix.clone();
916                    full_segments.extend(item.path.clone());
917                    let full = full_segments.join(".");
918                    let full_file = self.file_for_module_path(&full);
919                    if self.module_source_known(&full, &full_file) {
920                        continue;
921                    }
922
923                    let mut module_segments = full_segments.clone();
924                    let item_name = module_segments.pop().unwrap();
925                    let module_path = module_segments.join(".");
926                    let fq_name = if module_path.is_empty() {
927                        item_name.clone()
928                    } else {
929                        format!("{}.{}", module_path, item_name)
930                    };
931                    let classification = self.classify_import_target(&module_path, &item_name);
932                    if classification.import_type {
933                        exports.types.insert(alias_name.clone(), fq_name.clone());
934                    }
935
936                    if classification.import_value {
937                        exports.functions.insert(alias_name.clone(), fq_name);
938                    }
939                }
940
941                Ok(())
942            }
943
944            UseTree::Glob { prefix } => {
945                let module = prefix.join(".");
946                if let Some(loaded) = self.cache.get(&module) {
947                    for (n, fq) in &loaded.exports.types {
948                        exports.types.insert(n.clone(), fq.clone());
949                    }
950
951                    for (n, fq) in &loaded.exports.functions {
952                        exports.functions.insert(n.clone(), fq.clone());
953                    }
954                }
955
956                Ok(())
957            }
958        }
959    }
960
961    fn simple_name<'a>(&self, qualified: &'a str) -> &'a str {
962        qualified
963            .rsplit_once('.')
964            .map(|(_, n)| n)
965            .unwrap_or(qualified)
966    }
967
968    fn simple_tail<'a>(&self, module_path: &'a str) -> &'a str {
969        module_path
970            .rsplit_once('.')
971            .map(|(_, n)| n)
972            .unwrap_or(module_path)
973    }
974
975    fn module_source_known(&self, module_path: &str, file: &Path) -> bool {
976        file.exists()
977            || self.source_overrides.contains_key(file)
978            || self.cache.contains_key(module_path)
979    }
980
981    fn classify_import_target(&self, module_path: &str, item_name: &str) -> ImportResolution {
982        if module_path.is_empty() {
983            return ImportResolution::both();
984        }
985
986        if let Some(module) = self.cache.get(module_path) {
987            let has_value = module.exports.functions.contains_key(item_name);
988            let has_type = module.exports.types.contains_key(item_name);
989            if has_value || has_type {
990                return ImportResolution {
991                    import_value: has_value,
992                    import_type: has_type,
993                };
994            }
995        }
996
997        ImportResolution::both()
998    }
999
1000    fn file_for_module_path(&self, module_path: &str) -> PathBuf {
1001        let segments: Vec<&str> = module_path.split('.').collect();
1002        let candidates = self.resolve_dependency_roots(&segments);
1003        if !candidates.is_empty() {
1004            let mut fallback: Option<PathBuf> = None;
1005            for (root, consumed) in candidates.iter().rev() {
1006                let candidate = Self::path_from_root(root, &segments, *consumed);
1007                if candidate.exists() {
1008                    return candidate;
1009                }
1010                if fallback.is_none() {
1011                    fallback = Some(candidate);
1012                }
1013            }
1014            if let Some(path) = fallback {
1015                return path;
1016            }
1017        }
1018
1019        let mut fallback = self.base_dir.clone();
1020        for seg in &segments {
1021            fallback.push(seg);
1022        }
1023        fallback.set_extension("lust");
1024        fallback
1025    }
1026
1027    fn resolve_dependency_roots(&self, segments: &[&str]) -> Vec<(&ModuleRoot, usize)> {
1028        let mut matched: Vec<(&ModuleRoot, usize)> = Vec::new();
1029        let mut prefix_segments: Vec<&str> = Vec::new();
1030        for (index, segment) in segments.iter().enumerate() {
1031            prefix_segments.push(*segment);
1032            let key = prefix_segments.join(".");
1033            if let Some(roots) = self.module_roots.get(&key) {
1034                for root in roots {
1035                    matched.push((root, index + 1));
1036                }
1037            }
1038        }
1039        matched
1040    }
1041
1042    fn path_from_root(root: &ModuleRoot, segments: &[&str], consumed: usize) -> PathBuf {
1043        let mut path = root.base.clone();
1044        if consumed == segments.len() {
1045            if let Some(relative) = &root.root_module {
1046                path.push(relative);
1047            } else if let Some(last) = segments.last() {
1048                path.push(format!("{last}.lust"));
1049            }
1050            return path;
1051        }
1052        for seg in &segments[consumed..segments.len() - 1] {
1053            path.push(seg);
1054        }
1055        if let Some(last) = segments.last() {
1056            path.push(format!("{last}.lust"));
1057        }
1058        path
1059    }
1060
1061    fn module_path_for_file(path: &Path) -> String {
1062        let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or("");
1063        stem.to_string()
1064    }
1065}
1066
1067#[cfg(not(feature = "std"))]
1068pub struct ModuleLoader {
1069    cache: HashMap<String, LoadedModule>,
1070    visited: HashSet<String>,
1071}
1072
1073#[cfg(not(feature = "std"))]
1074impl ModuleLoader {
1075    pub fn new() -> Self {
1076        Self {
1077            cache: HashMap::new(),
1078            visited: HashSet::new(),
1079        }
1080    }
1081
1082    pub fn clear_cache(&mut self) {
1083        self.cache.clear();
1084        self.visited.clear();
1085    }
1086
1087    pub fn load_program_from_modules(
1088        &mut self,
1089        modules: Vec<LoadedModule>,
1090        entry_module: String,
1091    ) -> Program {
1092        Program {
1093            modules,
1094            entry_module,
1095        }
1096    }
1097}
1098
1099#[cfg(test)]
1100mod tests {
1101    use super::*;
1102    use std::fs;
1103    use std::time::{SystemTime, UNIX_EPOCH};
1104
1105    fn unique_temp_dir(prefix: &str) -> std::path::PathBuf {
1106        let nanos = SystemTime::now()
1107            .duration_since(UNIX_EPOCH)
1108            .unwrap_or_default()
1109            .as_nanos();
1110        let mut dir = std::env::temp_dir();
1111        dir.push(format!("{prefix}_{nanos}"));
1112        dir
1113    }
1114
1115    #[test]
1116    fn merges_multiple_script_chunks_into_single_init() {
1117        let dir = unique_temp_dir("lust_module_loader_test");
1118        fs::create_dir_all(&dir).unwrap();
1119        let entry_path = dir.join("main.lust");
1120        let module_path = dir.join("m.lust");
1121
1122        fs::write(&entry_path, "use m as m\n").unwrap();
1123
1124        // Interleaved script + declarations; parser will produce multiple ItemKind::Script chunks.
1125        fs::write(
1126            &module_path,
1127            r#"
1128local a: int = 1
1129
1130pub function f(): int
1131    return a
1132end
1133
1134local b: int = 2
1135
1136pub function g(): int
1137    return b
1138end
1139
1140local c: int = a + b
1141"#,
1142        )
1143        .unwrap();
1144
1145        let mut loader = ModuleLoader::new(dir.clone());
1146        let program = loader
1147            .load_program_from_entry(entry_path.to_str().unwrap())
1148            .unwrap();
1149
1150        let module = program.modules.iter().find(|m| m.path == "m").unwrap();
1151        assert_eq!(module.init_function.as_deref(), Some("__init@m"));
1152
1153        let init_functions: Vec<&FunctionDef> = module
1154            .items
1155            .iter()
1156            .filter_map(|item| match &item.kind {
1157                ItemKind::Function(f) if f.name == "__init@m" => Some(f),
1158                _ => None,
1159            })
1160            .collect();
1161        assert_eq!(init_functions.len(), 1);
1162        assert_eq!(init_functions[0].body.len(), 3);
1163
1164        // Best-effort cleanup.
1165        let _ = fs::remove_file(entry_path);
1166        let _ = fs::remove_file(module_path);
1167        let _ = fs::remove_dir(dir);
1168    }
1169
1170    #[test]
1171    fn lua_require_does_not_force_missing_modules_as_dependencies() {
1172        let dir = unique_temp_dir("lust_module_loader_lua_require_missing");
1173        fs::create_dir_all(&dir).unwrap();
1174        let entry_path = dir.join("main.lust");
1175        let module_path = dir.join("a.lust");
1176
1177        fs::write(&entry_path, "use a as a\n").unwrap();
1178
1179        // `lua.require("missing.module")` appears inside a closure (so it should remain optional).
1180        // The module loader must not try to resolve it as a hard on-disk dependency.
1181        fs::write(
1182            &module_path,
1183            r#"
1184use lua as lua
1185
1186local f = function(): unknown
1187    return lua.require("missing.module")
1188end
1189"#,
1190        )
1191        .unwrap();
1192
1193        let mut loader = ModuleLoader::new(dir.clone());
1194        let program = loader
1195            .load_program_from_entry(entry_path.to_str().unwrap())
1196            .unwrap();
1197
1198        assert!(program.modules.iter().any(|m| m.path == "a"));
1199        assert!(!program.modules.iter().any(|m| m.path == "missing.module"));
1200
1201        // Best-effort cleanup.
1202        let _ = fs::remove_file(entry_path);
1203        let _ = fs::remove_file(module_path);
1204        let _ = fs::remove_dir(dir);
1205    }
1206}