gruel_air/sema/
imports.rs

1//! Import resolution and const initializer evaluation.
2//!
3//! This module handles:
4//! - Evaluating const initializers (e.g., `const x = @import(...)`)
5//! - Resolving import paths to actual file paths
6//!
7//! Import path resolution uses the structured [`ModulePath`] type for clear,
8//! testable resolution logic with explicit priority order. See the module_path
9//! module for details on how different import forms are resolved.
10
11use gruel_error::{CompileError, CompileResult, ErrorKind};
12use gruel_span::Span;
13
14use crate::types::Type;
15
16use super::Sema;
17use super::module_path::ModulePath;
18
19impl Sema<'_> {
20    /// Evaluate const initializers to determine their types.
21    ///
22    /// This is Phase 2.5 of semantic analysis, called after declaration gathering
23    /// but before function body analysis. It handles:
24    ///
25    /// - `const x = @import("module")` - evaluates to Type::Module
26    /// - Other const initializers are left with placeholder types for now
27    ///
28    /// This enables module re-exports where a const holds an imported module
29    /// that can be accessed via dot notation.
30    pub fn evaluate_const_initializers(&mut self) -> CompileResult<()> {
31        // Collect const names to iterate (avoid borrowing issues)
32        let const_names: Vec<lasso::Spur> = self.constants.keys().copied().collect();
33
34        for name in const_names {
35            let const_info = self.constants.get(&name).unwrap();
36            let init_ref = const_info.init;
37            let span = const_info.span;
38
39            // Check if the init expression is an @import intrinsic
40            let inst = self.rir.get(init_ref);
41            if let gruel_rir::InstData::Intrinsic {
42                name: intrinsic_name,
43                args_start,
44                args_len,
45            } = &inst.data
46            {
47                let intrinsic_name_str = self.interner.resolve(intrinsic_name);
48                if intrinsic_name_str == "import" {
49                    // This is an @import - evaluate it at compile time
50                    let result = self.evaluate_import_intrinsic(*args_start, *args_len, span)?;
51
52                    // Update the const type to the module type
53                    if let Some(const_info_mut) = self.constants.get_mut(&name) {
54                        const_info_mut.ty = result;
55                    }
56                }
57            }
58        }
59
60        Ok(())
61    }
62
63    /// Evaluate an @import intrinsic call at compile time.
64    ///
65    /// This is used during const initializer evaluation to resolve module imports.
66    pub(crate) fn evaluate_import_intrinsic(
67        &mut self,
68        args_start: u32,
69        args_len: u32,
70        span: Span,
71    ) -> CompileResult<Type> {
72        // @import takes exactly one argument
73        if args_len != 1 {
74            return Err(CompileError::new(
75                ErrorKind::IntrinsicWrongArgCount {
76                    name: "import".to_string(),
77                    expected: 1,
78                    found: args_len as usize,
79                },
80                span,
81            ));
82        }
83
84        // Get the argument from extra data (intrinsics use inst_refs, not call_args)
85        let arg_refs = self.rir.get_inst_refs(args_start, args_len);
86        let arg_inst = self.rir.get(arg_refs[0]);
87
88        // The argument must be a string literal
89        let import_path = match &arg_inst.data {
90            gruel_rir::InstData::StringConst(path_spur) => {
91                self.interner.resolve(path_spur).to_string()
92            }
93            _ => {
94                return Err(CompileError::new(
95                    ErrorKind::ImportRequiresStringLiteral,
96                    arg_inst.span,
97                ));
98            }
99        };
100
101        // Resolve the import path
102        let resolved_path = self.resolve_import_path_for_const(&import_path, span)?;
103
104        // Register the module
105        let (module_id, _is_new) = self
106            .module_registry
107            .get_or_create(import_path, resolved_path);
108
109        Ok(Type::new_module(module_id))
110    }
111
112    /// Resolve an import path for const evaluation.
113    ///
114    /// This uses the structured `ModulePath` type for clear resolution logic.
115    /// See the module_path module for the resolution order and rules.
116    ///
117    /// # Resolution Order
118    ///
119    /// 1. Standard library (`"std"`) - currently not supported
120    /// 2. For explicit `.gruel` paths - exact match, then suffix match
121    /// 3. For simple paths - `{path}.gruel`, then suffix match, then basename match
122    /// 4. Facade files (`_foo.gruel`) for directory modules
123    pub(crate) fn resolve_import_path_for_const(
124        &self,
125        import_path: &str,
126        span: Span,
127    ) -> CompileResult<String> {
128        let module_path = ModulePath::parse(import_path);
129
130        // Try to resolve against loaded file paths
131        let loaded_paths = self.file_paths.values();
132        if let Some(resolved) = module_path.resolve(loaded_paths) {
133            return Ok(resolved);
134        }
135
136        // Module not found - collect candidates for error message
137        let candidates: Vec<String> = self.file_paths.values().cloned().collect();
138        Err(CompileError::new(
139            ErrorKind::ModuleNotFound {
140                path: import_path.to_string(),
141                candidates,
142            },
143            span,
144        ))
145    }
146}