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}