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}