gruel_air/sema/declarations.rs
1//! Declaration gathering for semantic analysis.
2//!
3//! This module handles the first phase of semantic analysis: gathering all
4//! type and function declarations from the RIR. This includes:
5//!
6//! - Registering struct and enum type names
7//! - Resolving struct field types
8//! - Collecting function signatures
9//! - Collecting method signatures from impl blocks
10//! - Validating @copy and @handle structs
11
12use std::collections::{HashMap, HashSet};
13
14use gruel_builtins::is_reserved_type_name;
15use gruel_error::{CompileError, CompileResult, CopyStructNonCopyFieldError, ErrorKind, ice};
16use gruel_rir::{InstData, InstRef, RirDirective, RirParamMode};
17use gruel_span::Span;
18use lasso::Spur;
19
20use super::{ConstInfo, FunctionInfo, InferenceContext, MethodInfo, Sema};
21use crate::inference::{FunctionSig, MethodSig};
22use crate::types::{EnumDef, EnumId, EnumVariantDef, StructDef, StructField, StructId, Type};
23
24impl<'a> Sema<'a> {
25 /// Build an `InferenceContext` from the collected type information.
26 ///
27 /// This should be called after the collection phase and builds the
28 /// pre-computed maps needed for Hindley-Milner type inference.
29 /// Building this once and reusing for all function analyses avoids
30 /// the O(n²) cost of rebuilding these maps per function.
31 ///
32 /// # Performance
33 ///
34 /// This converts all function/method signatures to use `InferType`
35 /// (which handles arrays structurally rather than by ID). This conversion
36 /// is done once instead of per-function.
37 pub fn build_inference_context(&self) -> InferenceContext {
38 // Build function signatures with InferType for constraint generation
39 let func_sigs: HashMap<Spur, FunctionSig> = self
40 .functions
41 .iter()
42 .map(|(name, info)| {
43 (
44 *name,
45 FunctionSig {
46 param_types: self
47 .param_arena
48 .types(info.params)
49 .iter()
50 .map(|t| self.type_to_infer_type(*t))
51 .collect(),
52 return_type: self.type_to_infer_type(info.return_type),
53 is_generic: info.is_generic,
54 param_modes: self.param_arena.modes(info.params).to_vec(),
55 param_comptime: self.param_arena.comptime(info.params).to_vec(),
56 param_names: self.param_arena.names(info.params).to_vec(),
57 return_type_sym: info.return_type_sym,
58 },
59 )
60 })
61 .collect();
62
63 // Build struct types map (name -> Type::new_struct(id))
64 let struct_types: HashMap<Spur, Type> = self
65 .structs
66 .iter()
67 .map(|(name, id)| (*name, Type::new_struct(*id)))
68 .collect();
69
70 // Build enum types map (name -> Type::new_enum(id))
71 let enum_types: HashMap<Spur, Type> = self
72 .enums
73 .iter()
74 .map(|(name, id)| (*name, Type::new_enum(*id)))
75 .collect();
76
77 // Build method signatures with InferType for constraint generation
78 let method_sigs: HashMap<(StructId, Spur), MethodSig> = self
79 .methods
80 .iter()
81 .map(|((struct_id, method_name), info)| {
82 (
83 (*struct_id, *method_name),
84 MethodSig {
85 struct_type: info.struct_type,
86 has_self: info.has_self,
87 param_types: self
88 .param_arena
89 .types(info.params)
90 .iter()
91 .map(|t| self.type_to_infer_type(*t))
92 .collect(),
93 return_type: self.type_to_infer_type(info.return_type),
94 },
95 )
96 })
97 .collect();
98
99 // Build enum method signatures for constraint generation
100 let enum_method_sigs: HashMap<(EnumId, Spur), MethodSig> = self
101 .enum_methods
102 .iter()
103 .map(|((enum_id, method_name), info)| {
104 (
105 (*enum_id, *method_name),
106 MethodSig {
107 struct_type: info.struct_type,
108 has_self: info.has_self,
109 param_types: self
110 .param_arena
111 .types(info.params)
112 .iter()
113 .map(|t| self.type_to_infer_type(*t))
114 .collect(),
115 return_type: self.type_to_infer_type(info.return_type),
116 },
117 )
118 })
119 .collect();
120
121 InferenceContext {
122 func_sigs,
123 struct_types,
124 enum_types,
125 method_sigs,
126 enum_method_sigs,
127 }
128 }
129 /// Check if a directive list contains the @copy directive
130 pub(crate) fn has_copy_directive(&self, directives: &[RirDirective]) -> bool {
131 let copy_sym = self.interner.get("copy");
132 for directive in directives {
133 if Some(directive.name) == copy_sym {
134 return true;
135 }
136 }
137 false
138 }
139
140 /// Check if a directive list contains the @handle directive
141 pub(crate) fn has_handle_directive(&self, directives: &[RirDirective]) -> bool {
142 let handle_sym = self.interner.get("handle");
143 for directive in directives {
144 if Some(directive.name) == handle_sym {
145 return true;
146 }
147 }
148 false
149 }
150 /// Phase 1: Register all type names (enum and struct IDs).
151 ///
152 /// This creates name → ID mappings for all enums and structs in a single pass,
153 /// allowing types to reference each other in any order. Struct definitions are
154 /// created with placeholder empty fields that will be filled in during phase 2.
155 pub(crate) fn register_type_names(&mut self) -> CompileResult<()> {
156 for (_, inst) in self.rir.iter() {
157 match &inst.data {
158 InstData::EnumDecl {
159 is_pub,
160 name,
161 variants_start,
162 variants_len,
163 } => {
164 let enum_name = self.interner.resolve(name).to_string();
165
166 // Check for collision with built-in type names
167 if is_reserved_type_name(&enum_name) {
168 return Err(CompileError::new(
169 ErrorKind::ReservedTypeName {
170 type_name: enum_name,
171 },
172 inst.span,
173 ));
174 }
175
176 // Check for duplicate type definitions (struct or enum with same name)
177 if self.enums.contains_key(name) || self.structs.contains_key(name) {
178 return Err(CompileError::new(
179 ErrorKind::DuplicateTypeDefinition {
180 type_name: enum_name,
181 },
182 inst.span,
183 ));
184 }
185
186 let raw_variants = self
187 .rir
188 .get_enum_variant_decls(*variants_start, *variants_len);
189
190 // Check for duplicate variant names
191 let mut seen_variants: HashSet<Spur> = HashSet::new();
192 for (variant_name, _, field_names) in &raw_variants {
193 if !seen_variants.insert(*variant_name) {
194 let variant_name_str = self.interner.resolve(variant_name).to_string();
195 return Err(CompileError::new(
196 ErrorKind::DuplicateVariant {
197 enum_name: enum_name.clone(),
198 variant_name: variant_name_str,
199 },
200 inst.span,
201 ));
202 }
203
204 if !field_names.is_empty() {
205 // Check for duplicate field names within the struct variant
206 let variant_str = self.interner.resolve(variant_name).to_string();
207 let mut seen_fields: HashSet<Spur> = HashSet::new();
208 for field_name in field_names {
209 if !seen_fields.insert(*field_name) {
210 let field_str = self.interner.resolve(field_name).to_string();
211 return Err(CompileError::new(
212 ErrorKind::DuplicateField {
213 struct_name: format!("{}::{}", enum_name, variant_str),
214 field_name: field_str,
215 },
216 inst.span,
217 ));
218 }
219 }
220 }
221 }
222
223 // Build EnumVariantDef list. Field types are stored as unit for now;
224 // full type resolution will be added in later phases when we
225 // lower them through the type checker.
226 let variants: Vec<EnumVariantDef> = raw_variants
227 .iter()
228 .map(|(vname, _fields, field_names)| EnumVariantDef {
229 name: self.interner.resolve(vname).to_string(),
230 fields: Vec::new(), // Field types resolved in later phases
231 field_names: field_names
232 .iter()
233 .map(|n| self.interner.resolve(n).to_string())
234 .collect(),
235 })
236 .collect();
237
238 let enum_def = EnumDef {
239 name: enum_name,
240 variants,
241 is_pub: *is_pub,
242 file_id: inst.span.file_id,
243 };
244
245 // Register in type pool and get pool-based EnumId
246 let (enum_id, _) = self.type_pool.register_enum(*name, enum_def);
247
248 // Register in enum lookup with pool-based EnumId
249 self.enums.insert(*name, enum_id);
250 }
251 InstData::StructDecl {
252 directives_start,
253 directives_len,
254 is_pub,
255 is_linear,
256 name,
257 ..
258 } => {
259 let struct_name = self.interner.resolve(name).to_string();
260
261 // Check for collision with built-in type names
262 if is_reserved_type_name(&struct_name) {
263 return Err(CompileError::new(
264 ErrorKind::ReservedTypeName {
265 type_name: struct_name,
266 },
267 inst.span,
268 ));
269 }
270
271 // Check for duplicate type definitions (struct or enum with same name)
272 if self.structs.contains_key(name) || self.enums.contains_key(name) {
273 return Err(CompileError::new(
274 ErrorKind::DuplicateTypeDefinition {
275 type_name: struct_name,
276 },
277 inst.span,
278 ));
279 }
280
281 let directives = self.rir.get_directives(*directives_start, *directives_len);
282 let is_copy = self.has_copy_directive(&directives);
283 let is_handle = self.has_handle_directive(&directives);
284
285 // Linear types cannot be @copy
286 if *is_linear && is_copy {
287 return Err(CompileError::new(
288 ErrorKind::LinearStructCopy(struct_name.clone()),
289 inst.span,
290 ));
291 }
292
293 // Create placeholder struct def (fields will be resolved in phase 2)
294 let struct_def = StructDef {
295 name: struct_name,
296 fields: Vec::new(), // Filled in during resolve_declarations
297 is_copy,
298 is_handle,
299 is_linear: *is_linear,
300 destructor: None, // Filled in during resolve_declarations
301 is_builtin: false, // User-defined struct
302 is_pub: *is_pub,
303 file_id: inst.span.file_id,
304 };
305
306 // Register in type pool and get pool-based StructId
307 let (struct_id, _) = self.type_pool.register_struct(*name, struct_def);
308
309 // Register in struct lookup with pool-based StructId
310 self.structs.insert(*name, struct_id);
311 }
312 _ => {}
313 }
314 }
315 Ok(())
316 }
317
318 /// Phase 2: Resolve all declarations.
319 ///
320 /// Now that all type names are registered, this resolves:
321 /// - Struct field types (must be done first for @copy validation)
322 /// - @copy struct validation, destructors, functions, and methods
323 ///
324 /// # Array Type Registration
325 ///
326 /// Array types from explicit type annotations (struct fields, function parameters,
327 /// return types, local variable annotations) are registered during this phase via
328 /// `resolve_type()` calls. Array types from literals (inferred during HM inference)
329 /// are created on-demand via the thread-safe `TypeInternPool` during function
330 /// body analysis.
331 pub(crate) fn resolve_declarations(&mut self) -> CompileResult<()> {
332 self.resolve_struct_fields()?;
333 self.resolve_enum_variant_fields()?;
334 self.resolve_remaining_declarations()?;
335 Ok(())
336 }
337
338 /// Resolve enum variant field types. Must run after all type names are registered
339 /// so that field types can reference other enums/structs.
340 pub(crate) fn resolve_enum_variant_fields(&mut self) -> CompileResult<()> {
341 for (_, inst) in self.rir.iter() {
342 if let InstData::EnumDecl {
343 name,
344 variants_start,
345 variants_len,
346 ..
347 } = &inst.data
348 {
349 let enum_id = match self.enums.get(name) {
350 Some(id) => *id,
351 None => continue, // not registered (shouldn't happen)
352 };
353
354 let raw_variants = self
355 .rir
356 .get_enum_variant_decls(*variants_start, *variants_len);
357 let has_data = raw_variants.iter().any(|(_, fields, _)| !fields.is_empty());
358 if !has_data {
359 continue; // unit-only enum, no field types to resolve
360 }
361
362 let mut resolved_variants = Vec::with_capacity(raw_variants.len());
363 for (vname, field_type_spurs, field_name_spurs) in &raw_variants {
364 let mut resolved_fields = Vec::with_capacity(field_type_spurs.len());
365 for field_ty_spur in field_type_spurs {
366 let field_ty = self.resolve_type(*field_ty_spur, inst.span)?;
367 resolved_fields.push(field_ty);
368 }
369 let field_names: Vec<String> = field_name_spurs
370 .iter()
371 .map(|n| self.interner.resolve(n).to_string())
372 .collect();
373 resolved_variants.push(EnumVariantDef {
374 name: self.interner.resolve(vname).to_string(),
375 fields: resolved_fields,
376 field_names,
377 });
378 }
379
380 let mut enum_def = self.type_pool.enum_def(enum_id);
381 enum_def.variants = resolved_variants;
382 self.type_pool.update_enum_def(enum_id, enum_def);
383 }
384 }
385 Ok(())
386 }
387
388 /// Resolve struct field types. Must run before @copy validation.
389 pub(crate) fn resolve_struct_fields(&mut self) -> CompileResult<()> {
390 for (_, inst) in self.rir.iter() {
391 if let InstData::StructDecl {
392 name,
393 fields_start,
394 fields_len,
395 ..
396 } = &inst.data
397 {
398 let name_str = self.interner.resolve(name).to_string();
399 // Verify the struct exists in our lookup table
400 if !self.structs.contains_key(name) {
401 return Err(CompileError::new(
402 ErrorKind::InternalError(
403 ice!(
404 "struct not found in struct map",
405 phase: "sema/declarations",
406 details: {
407 "struct_name" => name_str.to_string()
408 }
409 )
410 .to_string(),
411 ),
412 inst.span,
413 ));
414 }
415
416 // Get the struct ID from the lookup table
417 let struct_id = *self.structs.get(name).ok_or_else(|| {
418 CompileError::new(
419 ErrorKind::InternalError(
420 ice!(
421 "struct not found in structs map",
422 phase: "sema/declarations",
423 details: {
424 "struct_name" => name_str.to_string()
425 }
426 )
427 .to_string(),
428 ),
429 inst.span,
430 )
431 })?;
432
433 let struct_name = name_str.clone();
434 let fields = self.rir.get_field_decls(*fields_start, *fields_len);
435
436 // Check for duplicate field names
437 let mut seen_fields: HashSet<Spur> = HashSet::new();
438 for (field_name, _) in &fields {
439 if !seen_fields.insert(*field_name) {
440 let field_name_str = self.interner.resolve(field_name).to_string();
441 return Err(CompileError::new(
442 ErrorKind::DuplicateField {
443 struct_name,
444 field_name: field_name_str,
445 },
446 inst.span,
447 ));
448 }
449 }
450
451 // Resolve field types
452 let mut resolved_fields = Vec::new();
453 for (field_name, field_type) in &fields {
454 let field_ty = self.resolve_type(*field_type, inst.span)?;
455 resolved_fields.push(StructField {
456 name: self.interner.resolve(field_name).to_string(),
457 ty: field_ty,
458 });
459 }
460
461 // Update the struct definition in the pool with resolved fields
462 let mut struct_def = self.type_pool.struct_def(struct_id);
463 struct_def.fields = resolved_fields;
464 self.type_pool.update_struct_def(struct_id, struct_def);
465 }
466 }
467 Ok(())
468 }
469
470 /// Resolve @copy validation, destructors, functions, and methods.
471 pub(crate) fn resolve_remaining_declarations(&mut self) -> CompileResult<()> {
472 // Collect all method InstRefs from anonymous struct and enum types.
473 // These need to be skipped during function declaration collection because:
474 // - They may use `Self` type which requires struct/enum context
475 // - They are registered later during comptime evaluation with proper Self resolution
476 let mut anon_type_method_refs = std::collections::HashSet::new();
477 for (_, inst) in self.rir.iter() {
478 let (methods_start, methods_len) = match &inst.data {
479 InstData::AnonStructType {
480 methods_start,
481 methods_len,
482 ..
483 } => (*methods_start, *methods_len),
484 InstData::AnonEnumType {
485 methods_start,
486 methods_len,
487 ..
488 } => (*methods_start, *methods_len),
489 _ => continue,
490 };
491 let method_refs = self.rir.get_inst_refs(methods_start, methods_len);
492 for method_ref in method_refs {
493 anon_type_method_refs.insert(method_ref);
494 }
495 }
496
497 // First pass: collect all declarations and validate @copy structs
498 for (inst_ref, inst) in self.rir.iter() {
499 match &inst.data {
500 InstData::StructDecl {
501 directives_start,
502 directives_len,
503 name,
504 methods_start,
505 methods_len,
506 ..
507 } => {
508 self.validate_copy_struct(
509 *directives_start,
510 *directives_len,
511 *name,
512 inst.span,
513 )?;
514 // Collect methods defined inline in the struct
515 self.collect_struct_methods(*name, *methods_start, *methods_len, inst.span)?;
516 }
517
518 InstData::DropFnDecl { type_name, .. } => {
519 self.collect_destructor(*type_name, inst.span)?;
520 }
521
522 InstData::FnDecl {
523 is_pub,
524 is_unchecked,
525 name,
526 params_start,
527 params_len,
528 return_type,
529 body,
530 has_self,
531 ..
532 } => {
533 // Skip methods (has_self = true) - these are handled elsewhere:
534 // - Named struct methods are collected via ImplDecl
535 if *has_self {
536 continue;
537 }
538
539 // Skip ALL methods from anonymous types (structs and enums)
540 // These are registered during comptime evaluation with proper Self type context
541 if anon_type_method_refs.contains(&inst_ref) {
542 continue;
543 }
544 self.collect_function_signature(
545 *name,
546 (*params_start, *params_len),
547 *return_type,
548 *body,
549 inst.span,
550 *is_pub,
551 *is_unchecked,
552 )?;
553 }
554
555 InstData::ConstDecl {
556 is_pub, name, init, ..
557 } => {
558 self.collect_const_declaration(*name, *is_pub, *init, inst.span)?;
559 }
560
561 _ => {}
562 }
563 }
564
565 // Second pass: validate @handle structs (after all methods are collected)
566 self.validate_handle_structs()?;
567
568 Ok(())
569 }
570
571 /// Validate that a @copy struct only contains Copy type fields.
572 fn validate_copy_struct(
573 &self,
574 directives_start: u32,
575 directives_len: u32,
576 name: Spur,
577 span: Span,
578 ) -> CompileResult<()> {
579 let directives = self.rir.get_directives(directives_start, directives_len);
580 if !self.has_copy_directive(&directives) {
581 return Ok(());
582 }
583
584 let struct_name = self.interner.resolve(&name).to_string();
585 // Verify struct exists in our lookup
586 if !self.structs.contains_key(&name) {
587 return Err(CompileError::new(
588 ErrorKind::InternalError(
589 ice!(
590 "struct not found during @copy validation",
591 phase: "sema/declarations",
592 details: {
593 "struct_name" => struct_name.clone()
594 }
595 )
596 .to_string(),
597 ),
598 span,
599 ));
600 }
601
602 // Get the struct ID from the lookup table
603 let struct_id = *self.structs.get(&name).ok_or_else(|| {
604 CompileError::new(
605 ErrorKind::InternalError(
606 ice!(
607 "struct not found during @copy validation",
608 phase: "sema/declarations",
609 details: {
610 "struct_name" => struct_name.clone()
611 }
612 )
613 .to_string(),
614 ),
615 span,
616 )
617 })?;
618
619 // Get struct definition from the pool
620 let struct_def = self.type_pool.struct_def(struct_id);
621
622 for field in &struct_def.fields {
623 if !self.is_type_copy(field.ty) {
624 let field_type_name = self.format_type_name(field.ty);
625 return Err(CompileError::new(
626 ErrorKind::CopyStructNonCopyField(Box::new(CopyStructNonCopyFieldError {
627 struct_name,
628 field_name: field.name.clone(),
629 field_type: field_type_name,
630 })),
631 span,
632 ));
633 }
634 }
635 Ok(())
636 }
637
638 /// Validate that all @handle structs have a valid .handle() method.
639 ///
640 /// This runs after all methods are collected so we can look up
641 /// method signatures in the `methods` map.
642 pub(crate) fn validate_handle_structs(&self) -> CompileResult<()> {
643 // We need to iterate through structs and find their spans
644 for (_, inst) in self.rir.iter() {
645 if let InstData::StructDecl {
646 directives_start,
647 directives_len,
648 name,
649 ..
650 } = &inst.data
651 {
652 let directives = self.rir.get_directives(*directives_start, *directives_len);
653 if !self.has_handle_directive(&directives) {
654 continue;
655 }
656
657 let struct_name = self.interner.resolve(name).to_string();
658 let struct_id = *self.structs.get(name).ok_or_else(|| {
659 CompileError::new(
660 ErrorKind::InternalError(
661 ice!(
662 "struct not found during @handle validation",
663 phase: "sema/declarations",
664 details: {
665 "struct_name" => struct_name.clone()
666 }
667 )
668 .to_string(),
669 ),
670 inst.span,
671 )
672 })?;
673 let struct_type = Type::new_struct(struct_id);
674
675 // Look for a .handle() method using StructId
676 let handle_sym = self.interner.get("handle");
677 let method_key = match handle_sym {
678 Some(sym) => (struct_id, sym),
679 None => {
680 // "handle" not interned means no .handle() method exists
681 return Err(CompileError::new(
682 ErrorKind::HandleStructMissingMethod { struct_name },
683 inst.span,
684 ));
685 }
686 };
687
688 let method_info = match self.methods.get(&method_key) {
689 Some(info) => info,
690 None => {
691 return Err(CompileError::new(
692 ErrorKind::HandleStructMissingMethod { struct_name },
693 inst.span,
694 ));
695 }
696 };
697
698 // Validate: must be a method (has self), not associated function
699 if !method_info.has_self {
700 let param_types = self.param_arena.types(method_info.params);
701 let found_signature = format!(
702 "fn handle({}) -> {}",
703 param_types
704 .iter()
705 .map(|t| self.format_type_name(*t))
706 .collect::<Vec<_>>()
707 .join(", "),
708 self.format_type_name(method_info.return_type)
709 );
710 return Err(CompileError::new(
711 ErrorKind::HandleMethodWrongSignature {
712 struct_name,
713 found_signature,
714 },
715 method_info.span,
716 ));
717 }
718
719 // Validate: should take no extra parameters (just self)
720 let param_types = self.param_arena.types(method_info.params);
721 if !param_types.is_empty() {
722 let param_names = self.param_arena.names(method_info.params);
723 let params = std::iter::once(format!("self: {}", struct_name))
724 .chain(param_types.iter().zip(param_names).map(|(ty, name)| {
725 format!(
726 "{}: {}",
727 self.interner.resolve(name),
728 self.format_type_name(*ty)
729 )
730 }))
731 .collect::<Vec<_>>()
732 .join(", ");
733 let found_signature = format!(
734 "fn handle({}) -> {}",
735 params,
736 self.format_type_name(method_info.return_type)
737 );
738 return Err(CompileError::new(
739 ErrorKind::HandleMethodWrongSignature {
740 struct_name,
741 found_signature,
742 },
743 method_info.span,
744 ));
745 }
746
747 // Validate: return type must be the same struct type
748 if method_info.return_type != struct_type {
749 let found_signature = format!(
750 "fn handle(self: {}) -> {}",
751 struct_name,
752 self.format_type_name(method_info.return_type)
753 );
754 return Err(CompileError::new(
755 ErrorKind::HandleMethodWrongSignature {
756 struct_name,
757 found_signature,
758 },
759 method_info.span,
760 ));
761 }
762 }
763 }
764 Ok(())
765 }
766
767 /// Collect a destructor definition and register it with its struct.
768 fn collect_destructor(&mut self, type_name: Spur, span: Span) -> CompileResult<()> {
769 let type_name_str = self.interner.resolve(&type_name).to_string();
770
771 // Verify the struct exists
772 if !self.structs.contains_key(&type_name) {
773 return Err(CompileError::new(
774 ErrorKind::DestructorUnknownType {
775 type_name: type_name_str,
776 },
777 span,
778 ));
779 }
780
781 // Get the struct ID from the lookup table
782 let struct_id = *self.structs.get(&type_name).ok_or_else(|| {
783 CompileError::new(
784 ErrorKind::InternalError(
785 ice!(
786 "struct not found during destructor collection",
787 phase: "sema/declarations",
788 details: {
789 "struct_name" => type_name_str.to_string()
790 }
791 )
792 .to_string(),
793 ),
794 span,
795 )
796 })?;
797
798 let mut struct_def = self.type_pool.struct_def(struct_id);
799 if struct_def.destructor.is_some() {
800 return Err(CompileError::new(
801 ErrorKind::DuplicateDestructor {
802 type_name: type_name_str,
803 },
804 span,
805 ));
806 }
807
808 let destructor_name = format!("{}.__drop", type_name_str);
809 struct_def.destructor = Some(destructor_name);
810 self.type_pool.update_struct_def(struct_id, struct_def);
811 Ok(())
812 }
813
814 /// Collect a function signature for forward reference.
815 #[allow(clippy::too_many_arguments)]
816 fn collect_function_signature(
817 &mut self,
818 name: Spur,
819 (params_start, params_len): (u32, u32),
820 return_type_sym: Spur,
821 body: InstRef,
822 span: Span,
823 is_pub: bool,
824 is_unchecked: bool,
825 ) -> CompileResult<()> {
826 let params = self.rir.get_params(params_start, params_len);
827
828 let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
829 let param_modes: Vec<RirParamMode> = params.iter().map(|p| p.mode).collect();
830
831 // Check if this function has any comptime TYPE parameters (not value parameters).
832 // A type parameter is a comptime param where the type is `type`.
833 // - `comptime T: type` -> type parameter (is_generic = true)
834 // - `comptime n: i32` -> value parameter (is_generic = false)
835 let type_sym = self.interner.get_or_intern("type");
836 let is_generic = params.iter().any(|p| p.is_comptime && p.ty == type_sym);
837
838 // Collect type parameter names (comptime parameters whose type is `type`)
839 let type_param_names: Vec<Spur> = params
840 .iter()
841 .filter(|p| p.is_comptime && p.ty == type_sym)
842 .map(|p| p.name)
843 .collect();
844
845 // For generic functions, we defer type resolution of type parameters until specialization.
846 // We use Type::COMPTIME_TYPE as a placeholder for comptime T: type parameters.
847 let param_types: Vec<Type> = params
848 .iter()
849 .map(|p| {
850 if p.is_comptime && p.ty == type_sym {
851 // For comptime TYPE parameters (comptime T: type), the type is `type`
852 Ok(Type::COMPTIME_TYPE)
853 } else if type_param_names.contains(&p.ty) {
854 // This parameter's type is a type parameter (e.g., `x: T` where T is comptime)
855 // Use ComptimeType as a placeholder - actual type determined at specialization
856 Ok(Type::COMPTIME_TYPE)
857 } else {
858 // Regular params OR comptime VALUE params (comptime n: i32)
859 self.resolve_type(p.ty, span)
860 }
861 })
862 .collect::<CompileResult<Vec<_>>>()?;
863 let param_comptime: Vec<bool> = params.iter().map(|p| p.is_comptime).collect();
864
865 // For generic functions, we can't resolve the return type yet if it references
866 // a type parameter. For now, check if it matches any type parameter name.
867 let ret_type = if type_param_names.contains(&return_type_sym) {
868 // Return type is a type parameter - use placeholder
869 Type::COMPTIME_TYPE
870 } else {
871 self.resolve_type(return_type_sym, span)?
872 };
873
874 // Allocate parameter data in the arena
875 let params_range =
876 self.param_arena
877 .alloc(param_names, param_types, param_modes, param_comptime);
878
879 self.functions.insert(
880 name,
881 FunctionInfo {
882 params: params_range,
883 return_type: ret_type,
884 return_type_sym,
885 body,
886 span,
887 is_generic,
888 is_pub,
889 is_unchecked,
890 file_id: span.file_id,
891 },
892 );
893 Ok(())
894 }
895
896 /// Collect methods defined inline in a struct.
897 fn collect_struct_methods(
898 &mut self,
899 type_name: Spur,
900 methods_start: u32,
901 methods_len: u32,
902 span: Span,
903 ) -> CompileResult<()> {
904 let struct_id = match self.structs.get(&type_name) {
905 Some(id) => *id,
906 None => {
907 let type_name_str = self.interner.resolve(&type_name).to_string();
908 return Err(CompileError::new(
909 ErrorKind::UnknownType(type_name_str),
910 span,
911 ));
912 }
913 };
914 let struct_type = Type::new_struct(struct_id);
915
916 let methods = self.rir.get_inst_refs(methods_start, methods_len);
917 for method_ref in methods {
918 let method_inst = self.rir.get(method_ref);
919 if let InstData::FnDecl {
920 name: method_name,
921 is_unchecked,
922 params_start,
923 params_len,
924 return_type,
925 body,
926 has_self,
927 ..
928 } = &method_inst.data
929 {
930 // Use StructId in key to support anonymous struct methods
931 let key = (struct_id, *method_name);
932 if self.methods.contains_key(&key) {
933 let type_name_str = self.interner.resolve(&type_name).to_string();
934 let method_name_str = self.interner.resolve(method_name).to_string();
935 return Err(CompileError::new(
936 ErrorKind::DuplicateMethod {
937 type_name: type_name_str,
938 method_name: method_name_str,
939 },
940 method_inst.span,
941 ));
942 }
943
944 let params = self.rir.get_params(*params_start, *params_len);
945 let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
946 let param_types: Vec<Type> = params
947 .iter()
948 .map(|p| self.resolve_type(p.ty, method_inst.span))
949 .collect::<CompileResult<Vec<_>>>()?;
950 let ret_type = self.resolve_type(*return_type, method_inst.span)?;
951
952 // Allocate method parameters in the arena
953 let param_range = self
954 .param_arena
955 .alloc_method(param_names.into_iter(), param_types.into_iter());
956
957 self.methods.insert(
958 key,
959 MethodInfo {
960 struct_type,
961 has_self: *has_self,
962 params: param_range,
963 return_type: ret_type,
964 body: *body,
965 span: method_inst.span,
966 is_unchecked: *is_unchecked,
967 },
968 );
969 }
970 }
971 Ok(())
972 }
973
974 /// Collect a constant declaration.
975 ///
976 /// Constants are compile-time values. In the module system, they're primarily
977 /// used for re-exports:
978 /// ```gruel
979 /// pub const strings = @import("utils/strings.gruel");
980 /// ```
981 ///
982 /// When the initializer is an `@import(...)`, we evaluate it at compile time
983 /// to resolve the module and register it in the module registry. This enables
984 /// subsequent member access via `const_name.function()` syntax.
985 fn collect_const_declaration(
986 &mut self,
987 name: Spur,
988 is_pub: bool,
989 init: InstRef,
990 span: Span,
991 ) -> CompileResult<()> {
992 let name_str = self.interner.resolve(&name).to_string();
993
994 // Check for duplicate constant names
995 if self.constants.contains_key(&name) {
996 return Err(CompileError::new(
997 ErrorKind::DuplicateConstant {
998 name: name_str,
999 kind: "constant".to_string(),
1000 },
1001 span,
1002 ));
1003 }
1004
1005 // Check for collision with function names
1006 if self.functions.contains_key(&name) {
1007 return Err(CompileError::new(
1008 ErrorKind::DuplicateConstant {
1009 name: name_str.clone(),
1010 kind: "constant (conflicts with function)".to_string(),
1011 },
1012 span,
1013 ));
1014 }
1015
1016 // Evaluate the initializer at compile time to determine the constant type.
1017 // Currently we only handle @import(...) - other constant expressions will
1018 // be supported as part of the broader comptime feature (ADR-0025).
1019 let const_type = self.evaluate_const_init(init, span)?;
1020
1021 self.constants.insert(
1022 name,
1023 ConstInfo {
1024 is_pub,
1025 ty: const_type,
1026 init,
1027 span,
1028 },
1029 );
1030
1031 Ok(())
1032 }
1033
1034 /// Evaluate a constant initializer at compile time.
1035 ///
1036 /// Currently handles:
1037 /// - `@import("path")` - Returns Type::Module
1038 /// - Integer literals - Returns the integer type
1039 ///
1040 /// Future extensions (ADR-0025 comptime) will support:
1041 /// - Arithmetic on constants
1042 /// - comptime blocks
1043 /// - comptime function calls
1044 fn evaluate_const_init(&mut self, init: InstRef, span: Span) -> CompileResult<Type> {
1045 let init_inst = self.rir.get(init);
1046
1047 match &init_inst.data {
1048 // @import("path") evaluates to Type::Module at compile time
1049 InstData::Intrinsic {
1050 name,
1051 args_start,
1052 args_len,
1053 } => {
1054 if *name == self.known.import {
1055 // Validate exactly one argument
1056 if *args_len != 1 {
1057 return Err(CompileError::new(
1058 ErrorKind::IntrinsicWrongArgCount {
1059 name: "import".to_string(),
1060 expected: 1,
1061 found: *args_len as usize,
1062 },
1063 span,
1064 ));
1065 }
1066
1067 // Get the string literal argument
1068 let arg_refs = self.rir.get_inst_refs(*args_start, *args_len);
1069 let arg_inst = self.rir.get(arg_refs[0]);
1070 let import_path = match &arg_inst.data {
1071 InstData::StringConst(path_spur) => {
1072 self.interner.resolve(path_spur).to_string()
1073 }
1074 _ => {
1075 return Err(CompileError::new(
1076 ErrorKind::ImportRequiresStringLiteral,
1077 arg_inst.span,
1078 ));
1079 }
1080 };
1081
1082 // Resolve the import path to an absolute file path
1083 let resolved_path = self.resolve_import_path(&import_path, span)?;
1084
1085 // Register the module in the registry
1086 let (module_id, _is_new) = self
1087 .module_registry
1088 .get_or_create(import_path, resolved_path);
1089
1090 Ok(Type::new_module(module_id))
1091 } else {
1092 // For other intrinsics in const context, we don't support them yet
1093 let intrinsic_name = self.interner.resolve(name).to_string();
1094 Err(CompileError::new(
1095 ErrorKind::ConstExprNotSupported {
1096 expr_kind: format!("@{} intrinsic", intrinsic_name),
1097 },
1098 span,
1099 ))
1100 }
1101 }
1102
1103 // Integer literals evaluate to i32 (the default integer type)
1104 // Note: RIR doesn't distinguish between integer types at this level;
1105 // type inference happens later. For now, we treat all integer consts as i32.
1106 InstData::IntConst(_) => Ok(Type::I32),
1107
1108 // Boolean literals
1109 InstData::BoolConst(_) => Ok(Type::BOOL),
1110
1111 // Unit literal
1112 InstData::UnitConst => Ok(Type::UNIT),
1113
1114 // String literals
1115 InstData::StringConst(_) => {
1116 // String constants would need the String type
1117 // For now, we don't support them in const context
1118 Err(CompileError::new(
1119 ErrorKind::ConstExprNotSupported {
1120 expr_kind: "string literals".to_string(),
1121 },
1122 span,
1123 ))
1124 }
1125
1126 // Other expressions are not yet supported in const context
1127 _ => Err(CompileError::new(
1128 ErrorKind::ConstExprNotSupported {
1129 expr_kind: "this expression".to_string(),
1130 },
1131 span,
1132 )),
1133 }
1134 }
1135}