Skip to main content

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