Skip to main content

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 @derive(Copy) and @handle structs
11
12use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
13
14use gruel_builtins::{
15    Abi, BUILTIN_MARKERS, ItemKinds, MarkerKind, Posture, ThreadSafety, get_builtin_marker,
16    is_reserved_type_constructor_name,
17};
18use gruel_rir::{InstData, InstRef, RirParamMode};
19use gruel_util::Span;
20use gruel_util::{CompileError, CompileResult, ErrorKind, ice};
21use lasso::Spur;
22
23use super::anon_interfaces::decode_receiver_mode;
24use super::{ConstInfo, FunctionInfo, InferenceContext, MethodInfo, Sema};
25use crate::inference::{FunctionSig, MethodSig};
26use crate::types::parse_type_call_syntax;
27use crate::types::{
28    EnumDef, EnumId, EnumVariantDef, InterfaceDef, InterfaceId, InterfaceMethodReq, ReceiverMode,
29    StructDef, StructField, StructId, Type,
30};
31
32/// Posture declared by the user on a type definition (ADR-0080 / ADR-0083).
33///
34/// `Unmarked` is the default — the user did not write a `copy`/`linear`
35/// keyword nor any `@mark(posture)` directive. Under uniform structural
36/// inference (ADR-0083), unmarked types pick up their final posture from
37/// their members; marked types must be consistent with the declaration.
38#[derive(Debug, Clone, Copy, PartialEq, Eq)]
39enum DeclaredPosture {
40    Copy,
41    Affine,
42    Linear,
43    Unmarked,
44}
45
46impl DeclaredPosture {
47    fn as_str(self) -> &'static str {
48        match self {
49            DeclaredPosture::Copy => "copy",
50            DeclaredPosture::Affine => "affine",
51            DeclaredPosture::Linear => "linear",
52            DeclaredPosture::Unmarked => "unmarked",
53        }
54    }
55}
56
57/// ADR-0083 / ADR-0084: outcome of processing the `@mark(...)` directive
58/// list on a type declaration. Posture markers (`copy`/`affine`/`linear`)
59/// and thread-safety markers (`unsend`/`checked_send`/`checked_sync`)
60/// occupy independent slots — a type may carry one of each.
61#[derive(Debug, Clone, Copy, Default)]
62pub(crate) struct MarkOutcome {
63    pub copy: bool,
64    pub affine: bool,
65    pub linear: bool,
66    /// ADR-0084: thread-safety override (`unsend`, `checked_send`,
67    /// `checked_sync`). At most one is permitted per directive list.
68    /// Behind the `thread_safety` preview gate.
69    pub thread_safety_override: Option<ThreadSafety>,
70    /// ADR-0085: C ABI / C layout marker (`@mark(c)`). On a struct,
71    /// switches layout to C rules and makes the type eligible to cross
72    /// the FFI boundary by value. Behind the `c_ffi` preview gate.
73    pub c_layout: bool,
74}
75
76/// Posture observed on a member's type (ADR-0080).
77#[derive(Debug, Clone, Copy, PartialEq, Eq)]
78enum MemberPosture {
79    Copy,
80    Affine,
81    Linear,
82}
83
84impl MemberPosture {
85    fn as_str(self) -> &'static str {
86        match self {
87            MemberPosture::Copy => "Copy",
88            MemberPosture::Affine => "affine",
89            MemberPosture::Linear => "linear",
90        }
91    }
92}
93
94/// ADR-0080 / ADR-0083: compare a member's posture against the host's
95/// declared posture and produce a `PostureMismatch` error if the rule is
96/// violated.
97///
98/// Rules:
99///
100/// - `Copy` host: every member must be Copy.
101/// - `Affine` host (`@mark(affine)`): no member may be Linear (linear is
102///   contagious upward and cannot be hidden behind an affine declaration,
103///   ADR-0083).
104/// - `Linear` host: accepts anything.
105/// - `Unmarked`: no constraint here — under uniform inference the final
106///   posture is the inferred posture, never an error.
107fn check_posture_against_declared(
108    host_kind: &'static str,
109    host_name: &str,
110    declared: DeclaredPosture,
111    member_kind: &'static str,
112    member_name: &str,
113    member_type: &str,
114    member_posture: MemberPosture,
115    span: Span,
116) -> Option<CompileError> {
117    let violates = matches!(
118        (declared, member_posture),
119        (
120            DeclaredPosture::Copy,
121            MemberPosture::Affine | MemberPosture::Linear
122        ) | (DeclaredPosture::Affine, MemberPosture::Linear)
123    );
124    if !violates {
125        return None;
126    }
127    Some(CompileError::new(
128        ErrorKind::PostureMismatch(Box::new(gruel_util::PostureMismatchError {
129            host_kind,
130            host_name: host_name.to_string(),
131            declared_posture: declared.as_str(),
132            member_kind,
133            member_name: member_name.to_string(),
134            member_type: member_type.to_string(),
135            member_posture: member_posture.as_str(),
136        })),
137        span,
138    ))
139}
140
141/// Signature data for a `drop` method, bundled for inline-drop registration.
142struct DropSignature {
143    has_self: bool,
144    params_len: u32,
145    return_type: Spur,
146    body: InstRef,
147    span: Span,
148}
149
150/// Visibility / safety flags on a top-level function declaration.
151#[derive(Clone, Copy)]
152struct FnFlags {
153    is_pub: bool,
154    is_unchecked: bool,
155    /// Index into the extra array where this fn's directives start
156    /// (ADR-0085: used to detect `@mark(c)` / `@link_name(...)`).
157    directives_start: u32,
158    /// Number of directives.
159    directives_len: u32,
160}
161
162impl<'a> Sema<'a> {
163    /// Build an `InferenceContext` from the collected type information.
164    ///
165    /// This should be called after the collection phase and builds the
166    /// pre-computed maps needed for Hindley-Milner type inference.
167    /// Building this once and reusing for all function analyses avoids
168    /// the O(n²) cost of rebuilding these maps per function.
169    ///
170    /// # Performance
171    ///
172    /// This converts all function/method signatures to use `InferType`
173    /// (which handles arrays structurally rather than by ID). This conversion
174    /// is done once instead of per-function.
175    pub fn build_inference_context(&self) -> InferenceContext {
176        // Build function signatures with InferType for constraint generation
177        let func_sigs: HashMap<Spur, FunctionSig> = self
178            .functions
179            .iter()
180            .map(|(name, info)| {
181                (
182                    *name,
183                    FunctionSig {
184                        param_types: self
185                            .param_arena
186                            .types(info.params)
187                            .iter()
188                            .map(|t| self.type_to_infer_type(*t))
189                            .collect(),
190                        return_type: self.type_to_infer_type(info.return_type),
191                        is_generic: info.is_generic,
192                        param_modes: self.param_arena.modes(info.params).to_vec(),
193                        param_comptime: self.param_arena.comptime(info.params).to_vec(),
194                        param_names: self.param_arena.names(info.params).to_vec(),
195                        return_type_sym: info.return_type_sym,
196                    },
197                )
198            })
199            .collect();
200
201        // Build struct types map (name -> Type::new_struct(id))
202        let struct_types: HashMap<Spur, Type> = self
203            .structs
204            .iter()
205            .map(|(name, id)| (*name, Type::new_struct(*id)))
206            .collect();
207
208        // Build enum types map (name -> Type::new_enum(id))
209        let enum_types: HashMap<Spur, Type> = self
210            .enums
211            .iter()
212            .map(|(name, id)| (*name, Type::new_enum(*id)))
213            .collect();
214
215        // Build method signatures with InferType for constraint generation.
216        // Method-level-generic methods (ADR-0055) are excluded: their stored
217        // `return_type` is a `COMPTIME_TYPE` placeholder, and their param
218        // types may reference unresolved method-level type params. The
219        // inference pass falls back to fresh type variables for calls to
220        // these methods, and the specialized bodies (synthesized by
221        // `specialize::create_specialized`) are analyzed separately with the
222        // concrete substitutions in place.
223        let method_sigs: HashMap<(StructId, Spur), MethodSig> = self
224            .methods
225            .iter()
226            .filter(|(_, info)| !info.is_generic)
227            .map(|((struct_id, method_name), info)| {
228                (
229                    (*struct_id, *method_name),
230                    MethodSig {
231                        struct_type: info.struct_type,
232                        has_self: info.has_self,
233                        param_types: self
234                            .param_arena
235                            .types(info.params)
236                            .iter()
237                            .map(|t| self.type_to_infer_type(*t))
238                            .collect(),
239                        return_type: self.type_to_infer_type(info.return_type),
240                    },
241                )
242            })
243            .collect();
244
245        // Build enum method signatures for constraint generation
246        let enum_method_sigs: HashMap<(EnumId, Spur), MethodSig> = self
247            .enum_methods
248            .iter()
249            .map(|((enum_id, method_name), info)| {
250                (
251                    (*enum_id, *method_name),
252                    MethodSig {
253                        struct_type: info.struct_type,
254                        has_self: info.has_self,
255                        param_types: self
256                            .param_arena
257                            .types(info.params)
258                            .iter()
259                            .map(|t| self.type_to_infer_type(*t))
260                            .collect(),
261                        return_type: self.type_to_infer_type(info.return_type),
262                    },
263                )
264            })
265            .collect();
266
267        InferenceContext {
268            func_sigs,
269            struct_types,
270            enum_types,
271            method_sigs,
272            enum_method_sigs,
273        }
274    }
275    // ADR-0080 retired `has_copy_directive`: `@derive(Copy)` no longer
276    // resolves to a known interface, so the directive falls through the
277    // existing "unknown interface" derive resolver path.
278
279    /// ADR-0075: walk every directive collected during parsing and reject any
280    /// whose name isn't in the recognized set (`@allow`, `@derive`). Retired
281    /// directives (`@handle`, `@copy`) get a targeted retirement note; other
282    /// unknowns get an edit-distance suggestion when there's a near-match.
283    pub(crate) fn validate_directives(&self) -> CompileResult<()> {
284        for (_, inst) in self.rir.iter() {
285            let (start, len, _is_fn_decl) = match &inst.data {
286                InstData::StructDecl {
287                    directives_start,
288                    directives_len,
289                    ..
290                }
291                | InstData::Alloc {
292                    directives_start,
293                    directives_len,
294                    ..
295                }
296                | InstData::ConstDecl {
297                    directives_start,
298                    directives_len,
299                    ..
300                }
301                | InstData::AnonStructType {
302                    directives_start,
303                    directives_len,
304                    ..
305                }
306                | InstData::AnonEnumType {
307                    directives_start,
308                    directives_len,
309                    ..
310                } => (*directives_start, *directives_len, false),
311                InstData::FnDecl {
312                    directives_start,
313                    directives_len,
314                    ..
315                } => (*directives_start, *directives_len, true),
316                _ => continue,
317            };
318            if len == 0 {
319                continue;
320            }
321            for directive in self.rir.get_directives(start, len) {
322                let name = self.interner.resolve(&directive.name).to_string();
323                if name == "allow"
324                    || name == "derive"
325                    || name == "lang"
326                    || name == "mark"
327                    || name == "link_name"
328                {
329                    continue;
330                }
331                let note = directive_diagnosis_note(&name);
332                return Err(CompileError::new(
333                    ErrorKind::UnknownDirective { name, note },
334                    directive.span,
335                ));
336            }
337        }
338        Ok(())
339    }
340
341    /// Phase 1: Register all type names (enum and struct IDs).
342    ///
343    /// This creates name → ID mappings for all enums and structs in a single pass,
344    /// allowing types to reference each other in any order. Struct definitions are
345    /// created with placeholder empty fields that will be filled in during phase 2.
346    /// Validate and register `interface` declarations (ADR-0056).
347    ///
348    /// - Gates each declaration behind the `interfaces` preview feature.
349    /// - Rejects duplicate method names within a single interface.
350    /// - Rejects collisions between interface names and existing structs/enums
351    ///   (and other interfaces).
352    /// - Resolves each method signature's parameter and return types and
353    ///   stores an `InterfaceDef` on `Sema`.
354    ///
355    /// Conformance and dispatch wiring land in later phases.
356    pub(crate) fn validate_interface_decls(&mut self) -> CompileResult<()> {
357        // Two-pass walk: first collect raw decls (snapshotting RIR data so we
358        // can mutate Sema while resolving types), then register each one.
359        struct RawIface {
360            name: Spur,
361            is_pub: bool,
362            file_id: gruel_util::FileId,
363            decl_span: Span,
364            methods: Vec<RawIfaceMethod>,
365        }
366        struct RawIfaceMethod {
367            name: Spur,
368            params: Vec<(Spur, Spur)>, // (param_name, type_symbol)
369            return_type_sym: Spur,
370            receiver: ReceiverMode,
371            /// ADR-0088: whether this interface method signature is
372            /// `@mark(unchecked)`.
373            is_unchecked: bool,
374            span: Span,
375        }
376
377        let mut raw: Vec<RawIface> = Vec::new();
378        for (_, inst) in self.rir.iter() {
379            if let InstData::InterfaceDecl {
380                is_pub,
381                name,
382                methods_start,
383                methods_len,
384                directives_start,
385                directives_len,
386            } = &inst.data
387            {
388                // ADR-0088: `@mark(...)` is not legal on interface
389                // declaration heads. Interfaces don't carry ABI
390                // (`@mark(c)`), posture (`@mark(copy|affine|linear)`),
391                // thread-safety overrides, or per-fn `@mark(unchecked)`
392                // — those all attach to fn / struct / enum
393                // declarations. `@lang("…")` is the only directive
394                // currently legal on an interface head.
395                let directives = self.rir.get_directives(*directives_start, *directives_len);
396                for d in directives.iter() {
397                    if self.interner.resolve(&d.name) != "mark" {
398                        continue;
399                    }
400                    // Any `@mark(arg)` on an interface head is rejected
401                    // — surface the first arg's name in the diagnostic.
402                    if let Some(arg) = d.args.first() {
403                        let arg_name = self.interner.resolve(arg);
404                        return Err(CompileError::new(
405                            ErrorKind::MarkerNotApplicable {
406                                marker: arg_name.to_string(),
407                                item_kind: "interface",
408                            },
409                            d.span,
410                        ));
411                    }
412                }
413                let method_refs = self.rir.get_inst_refs(*methods_start, *methods_len);
414                let mut seen: HashSet<Spur> = HashSet::default();
415                let mut methods = Vec::new();
416                for method_ref in method_refs {
417                    let m = self.rir.get(method_ref);
418                    if let InstData::InterfaceMethodSig {
419                        name: method_name,
420                        params_start,
421                        params_len,
422                        return_type,
423                        receiver_mode,
424                        is_unchecked,
425                        directives_start,
426                        directives_len,
427                    } = &m.data
428                    {
429                        if !seen.insert(*method_name) {
430                            let iface_name = self.interner.resolve(name).to_string();
431                            let method_name_str = self.interner.resolve(method_name).to_string();
432                            return Err(CompileError::new(
433                                ErrorKind::DuplicateMethod {
434                                    type_name: format!("interface `{}`", iface_name),
435                                    method_name: method_name_str,
436                                },
437                                m.span,
438                            ));
439                        }
440
441                        // ADR-0088: only `@mark(unchecked)` is legal on
442                        // interface method signatures. Reject any other
443                        // marker (e.g. `@mark(c)`, `@mark(copy)`).
444                        let method_directives =
445                            self.rir.get_directives(*directives_start, *directives_len);
446                        for d in method_directives.iter() {
447                            if self.interner.resolve(&d.name) != "mark" {
448                                continue;
449                            }
450                            for arg in &d.args {
451                                let arg_name = self.interner.resolve(arg);
452                                if arg_name != "unchecked" {
453                                    return Err(CompileError::new(
454                                        ErrorKind::MarkerNotApplicable {
455                                            marker: arg_name.to_string(),
456                                            item_kind: "interface method",
457                                        },
458                                        d.span,
459                                    ));
460                                }
461                            }
462                        }
463                        let params = self
464                            .rir
465                            .get_params(*params_start, *params_len)
466                            .into_iter()
467                            .map(|p| (p.name, p.ty))
468                            .collect();
469                        let receiver = match *receiver_mode {
470                            1 => ReceiverMode::MutRef,
471                            2 => ReceiverMode::Ref,
472                            _ => ReceiverMode::ByValue,
473                        };
474                        methods.push(RawIfaceMethod {
475                            name: *method_name,
476                            params,
477                            return_type_sym: *return_type,
478                            receiver,
479                            is_unchecked: *is_unchecked,
480                            span: m.span,
481                        });
482                    }
483                }
484
485                raw.push(RawIface {
486                    name: *name,
487                    is_pub: *is_pub,
488                    file_id: inst.span.file_id,
489                    decl_span: inst.span,
490                    methods,
491                });
492            }
493        }
494
495        for r in raw {
496            // Reject collision with existing structs/enums/interfaces.
497            let name_str = self.interner.resolve(&r.name).to_string();
498            if self.structs.contains_key(&r.name)
499                || self.enums.contains_key(&r.name)
500                || self.interfaces.contains_key(&r.name)
501            {
502                return Err(CompileError::new(
503                    ErrorKind::DuplicateTypeDefinition {
504                        type_name: format!("interface `{}`", name_str),
505                    },
506                    r.decl_span,
507                ));
508            }
509
510            // Resolve each method's parameter and return slots, recognizing
511            // the symbol `Self` as `IfaceTy::SelfType` (ADR-0060). Receiver
512            // mode is `ByValue` until Phase 2 threads `inout`/`borrow self`
513            // from the parser.
514            let mut resolved_methods = Vec::with_capacity(r.methods.len());
515            for m in &r.methods {
516                let mut param_types = Vec::with_capacity(m.params.len());
517                for (_pname, ty_sym) in &m.params {
518                    param_types.push(self.resolve_iface_ty(*ty_sym, m.span)?);
519                }
520                let return_type = self.resolve_iface_ty(m.return_type_sym, m.span)?;
521                resolved_methods.push(InterfaceMethodReq {
522                    name: self.interner.resolve(&m.name).to_string(),
523                    receiver: m.receiver,
524                    param_types,
525                    return_type,
526                    is_unchecked: m.is_unchecked,
527                });
528            }
529
530            let id = InterfaceId(self.interface_defs.len() as u32);
531            self.interface_defs.push(InterfaceDef {
532                name: name_str,
533                methods: resolved_methods,
534                is_pub: r.is_pub,
535                file_id: r.file_id,
536            });
537            self.interfaces.insert(r.name, id);
538        }
539
540        Ok(())
541    }
542
543    pub(crate) fn register_type_names(&mut self) -> CompileResult<()> {
544        for (_, inst) in self.rir.iter() {
545            match &inst.data {
546                InstData::EnumDecl {
547                    is_pub,
548                    name,
549                    variants_start,
550                    variants_len,
551                    directives_start,
552                    directives_len,
553                    ..
554                } => {
555                    let enum_name = self.interner.resolve(name).to_string();
556
557                    // ADR-0083 Phase 4: posture is declared exclusively via
558                    // the `@mark(...)` directive — the keyword path retired.
559                    // `@mark(affine)` is tracked separately on `MarkOutcome`
560                    // because `Posture::Affine` is the default and the
561                    // marker existing affects later inference suppression.
562                    let mark_outcome = self.process_mark_directives(
563                        *directives_start,
564                        *directives_len,
565                        "enum",
566                        ItemKinds::ENUM,
567                        inst.span,
568                    )?;
569
570                    if mark_outcome.copy && mark_outcome.linear {
571                        return Err(CompileError::new(
572                            ErrorKind::LinearStructCopy(enum_name.clone()),
573                            inst.span,
574                        ));
575                    }
576                    // ADR-0083: `@mark(affine)` cannot coexist with Copy or
577                    // Linear declarations.
578                    if mark_outcome.affine && (mark_outcome.copy || mark_outcome.linear) {
579                        return Err(CompileError::new(
580                            ErrorKind::LinearStructCopy(enum_name.clone()),
581                            inst.span,
582                        ));
583                    }
584                    let posture = if mark_outcome.copy {
585                        Posture::Copy
586                    } else if mark_outcome.linear {
587                        Posture::Linear
588                    } else {
589                        Posture::Affine
590                    };
591
592                    // Check for collision with built-in type constructors
593                    // (e.g. Ptr, MutPtr — see ADR-0061). ADR-0081 retired
594                    // the BUILTIN_TYPES registry; collisions with prelude
595                    // types are caught by the duplicate-type check below.
596                    if is_reserved_type_constructor_name(&enum_name) {
597                        return Err(CompileError::new(
598                            ErrorKind::ReservedTypeName {
599                                type_name: enum_name,
600                            },
601                            inst.span,
602                        ));
603                    }
604
605                    // Check for duplicate type definitions (struct or enum with same name)
606                    if self.enums.contains_key(name) || self.structs.contains_key(name) {
607                        return Err(CompileError::new(
608                            ErrorKind::DuplicateTypeDefinition {
609                                type_name: enum_name,
610                            },
611                            inst.span,
612                        ));
613                    }
614
615                    let raw_variants = self
616                        .rir
617                        .get_enum_variant_decls(*variants_start, *variants_len);
618
619                    // Check for duplicate variant names
620                    let mut seen_variants: HashSet<Spur> = HashSet::default();
621                    for (variant_name, _, field_names) in &raw_variants {
622                        if !seen_variants.insert(*variant_name) {
623                            let variant_name_str = self.interner.resolve(variant_name).to_string();
624                            return Err(CompileError::new(
625                                ErrorKind::DuplicateVariant {
626                                    enum_name: enum_name.clone(),
627                                    variant_name: variant_name_str,
628                                },
629                                inst.span,
630                            ));
631                        }
632
633                        if !field_names.is_empty() {
634                            // Check for duplicate field names within the struct variant
635                            let variant_str = self.interner.resolve(variant_name).to_string();
636                            let mut seen_fields: HashSet<Spur> = HashSet::default();
637                            for field_name in field_names {
638                                if !seen_fields.insert(*field_name) {
639                                    let field_str = self.interner.resolve(field_name).to_string();
640                                    return Err(CompileError::new(
641                                        ErrorKind::DuplicateField {
642                                            struct_name: format!("{}::{}", enum_name, variant_str),
643                                            field_name: field_str,
644                                        },
645                                        inst.span,
646                                    ));
647                                }
648                            }
649                        }
650                    }
651
652                    // Build EnumVariantDef list. Field types are stored as unit for now;
653                    // full type resolution will be added in later phases when we
654                    // lower them through the type checker.
655                    let variants: Vec<EnumVariantDef> = raw_variants
656                        .iter()
657                        .map(|(vname, _fields, field_names)| EnumVariantDef {
658                            name: self.interner.resolve(vname).to_string(),
659                            fields: Vec::new(), // Field types resolved in later phases
660                            field_names: field_names
661                                .iter()
662                                .map(|n| self.interner.resolve(n).to_string())
663                                .collect(),
664                        })
665                        .collect();
666
667                    let enum_def = EnumDef {
668                        name: enum_name,
669                        variants,
670                        posture,
671                        // ADR-0084: placeholder. The structural minimum
672                        // and any `@mark(...)` override are folded in
673                        // by `validate_consistency` once fields resolve.
674                        // Defaulting to the user's explicit override (if
675                        // any) lets `is_thread_safety_type` give a sane
676                        // answer in the rare cases that consult the
677                        // field before the inference pass runs.
678                        thread_safety: mark_outcome
679                            .thread_safety_override
680                            .unwrap_or(ThreadSafety::Sync),
681                        is_pub: *is_pub,
682                        file_id: inst.span.file_id,
683                        destructor: None,
684                        // ADR-0086: `@mark(c)` flag flows from the
685                        // directive walker via `mark_outcome.c_layout`.
686                        is_c_layout: mark_outcome.c_layout,
687                    };
688
689                    // Register in type pool and get pool-based EnumId
690                    let (enum_id, _) = self.type_pool.register_enum(*name, enum_def);
691
692                    // Register in enum lookup with pool-based EnumId
693                    self.enums.insert(*name, enum_id);
694
695                    if mark_outcome.affine {
696                        self.mark_affine_decls.insert(*name);
697                    }
698                    // ADR-0084: track thread-safety overrides so
699                    // `validate_consistency` can apply them after
700                    // computing the structural minimum.
701                    if let Some(level) = mark_outcome.thread_safety_override {
702                        self.mark_thread_safety_decls.insert(*name, level);
703                    }
704                }
705                InstData::StructDecl {
706                    directives_start,
707                    directives_len,
708                    is_pub,
709                    name,
710                    ..
711                } => {
712                    let struct_name = self.interner.resolve(name).to_string();
713
714                    // Check for collision with built-in type constructors
715                    // (e.g. Ptr, MutPtr — see ADR-0061). ADR-0081 retired
716                    // the BUILTIN_TYPES registry; collisions with prelude
717                    // types are caught by the duplicate-type check below.
718                    if is_reserved_type_constructor_name(&struct_name) {
719                        return Err(CompileError::new(
720                            ErrorKind::ReservedTypeName {
721                                type_name: struct_name,
722                            },
723                            inst.span,
724                        ));
725                    }
726
727                    // Check for duplicate type definitions (struct or enum with same name)
728                    if self.structs.contains_key(name) || self.enums.contains_key(name) {
729                        return Err(CompileError::new(
730                            ErrorKind::DuplicateTypeDefinition {
731                                type_name: struct_name,
732                            },
733                            inst.span,
734                        ));
735                    }
736
737                    // ADR-0083 Phase 4: posture is declared exclusively via
738                    // the `@mark(...)` directive — the keyword path retired.
739                    let mark_outcome = self.process_mark_directives(
740                        *directives_start,
741                        *directives_len,
742                        "struct",
743                        ItemKinds::STRUCT,
744                        inst.span,
745                    )?;
746
747                    // Linear types cannot be Copy.
748                    if mark_outcome.linear && mark_outcome.copy {
749                        return Err(CompileError::new(
750                            ErrorKind::LinearStructCopy(struct_name.clone()),
751                            inst.span,
752                        ));
753                    }
754                    if mark_outcome.affine && (mark_outcome.copy || mark_outcome.linear) {
755                        return Err(CompileError::new(
756                            ErrorKind::LinearStructCopy(struct_name.clone()),
757                            inst.span,
758                        ));
759                    }
760                    let posture = if mark_outcome.copy {
761                        Posture::Copy
762                    } else if mark_outcome.linear {
763                        Posture::Linear
764                    } else {
765                        Posture::Affine
766                    };
767
768                    // Create placeholder struct def (fields will be resolved in phase 2)
769                    let struct_def = StructDef {
770                        name: struct_name,
771                        fields: Vec::new(), // Filled in during resolve_declarations
772                        posture,
773                        is_clone: false, // Filled in during resolve_declarations after fields known
774                        // ADR-0084: see EnumDecl above for rationale.
775                        thread_safety: mark_outcome
776                            .thread_safety_override
777                            .unwrap_or(ThreadSafety::Sync),
778                        destructor: None,  // Filled in during resolve_declarations
779                        is_builtin: false, // User-defined struct
780                        is_pub: *is_pub,
781                        file_id: inst.span.file_id,
782                        // ADR-0085: track whether `@mark(c)` was applied.
783                        is_c_layout: mark_outcome.c_layout,
784                    };
785
786                    // Register in type pool and get pool-based StructId
787                    let (struct_id, _) = self.type_pool.register_struct(*name, struct_def);
788
789                    // Register in struct lookup with pool-based StructId
790                    self.structs.insert(*name, struct_id);
791
792                    if mark_outcome.affine {
793                        self.mark_affine_decls.insert(*name);
794                    }
795                    // ADR-0084: track thread-safety overrides — see
796                    // EnumDecl above.
797                    if let Some(level) = mark_outcome.thread_safety_override {
798                        self.mark_thread_safety_decls.insert(*name, level);
799                    }
800                }
801                _ => {}
802            }
803        }
804        Ok(())
805    }
806
807    /// ADR-0083 / ADR-0084: apply `@mark(...)` directives recorded on
808    /// an anonymous struct expression to its already-created `StructDef`.
809    /// Mirrors what `validate_consistency` does for named declarations:
810    /// reads the marker outcome, then writes posture and thread-safety
811    /// fields. Called from the comptime evaluator and analysis paths
812    /// after `find_or_create_anon_struct` returns a fresh `StructId`.
813    pub(crate) fn apply_anon_struct_marks(
814        &mut self,
815        struct_id: StructId,
816        directives_start: u32,
817        directives_len: u32,
818        span: Span,
819    ) -> CompileResult<()> {
820        let outcome = self.process_mark_directives(
821            directives_start,
822            directives_len,
823            "struct",
824            ItemKinds::STRUCT,
825            span,
826        )?;
827        let mut def = self.type_pool.struct_def(struct_id);
828        // Posture: a marker overrides the structurally-inferred posture
829        // that `find_or_create_anon_struct` set.
830        if outcome.copy {
831            def.posture = Posture::Copy;
832        }
833        if outcome.linear {
834            def.posture = Posture::Linear;
835        }
836        if outcome.affine {
837            def.posture = Posture::Affine;
838        }
839        // Thread-safety: marker wins over the structural minimum the
840        // anon-struct constructor pre-computed.
841        if let Some(level) = outcome.thread_safety_override {
842            def.thread_safety = level;
843        }
844        self.type_pool.update_struct_def(struct_id, def);
845        Ok(())
846    }
847
848    /// Same as `apply_anon_struct_marks`, but for anonymous enums
849    /// (variant-payload structural fold + override).
850    pub(crate) fn apply_anon_enum_marks(
851        &mut self,
852        enum_id: EnumId,
853        directives_start: u32,
854        directives_len: u32,
855        span: Span,
856    ) -> CompileResult<()> {
857        let outcome = self.process_mark_directives(
858            directives_start,
859            directives_len,
860            "enum",
861            ItemKinds::ENUM,
862            span,
863        )?;
864        let mut def = self.type_pool.enum_def(enum_id);
865        if outcome.copy {
866            def.posture = Posture::Copy;
867        }
868        if outcome.linear {
869            def.posture = Posture::Linear;
870        }
871        if outcome.affine {
872            def.posture = Posture::Affine;
873        }
874        if let Some(level) = outcome.thread_safety_override {
875            def.thread_safety = level;
876        }
877        self.type_pool.update_enum_def(enum_id, def);
878        Ok(())
879    }
880
881    /// ADR-0083: process the `@mark(...)` directives on a type declaration.
882    ///
883    /// Walks the directive list and validates each marker against the
884    /// `BUILTIN_MARKERS` registry. Returns a flag-set describing which
885    /// posture markers were declared so the caller can fold them into the
886    /// type's `posture` field (and the `mark_affine_decls`
887    /// side set).
888    ///
889    /// Errors out on:
890    /// - Unknown markers (with edit-distance suggestions).
891    /// - Markers not applicable to the host item kind.
892    /// - Conflicting posture markers within the same directive list (e.g.
893    ///   `@mark(copy)` + `@mark(linear)`).
894    /// - Conflicting thread-safety markers (e.g. `@mark(unsend)` +
895    ///   `@mark(checked_send)`) — ADR-0084.
896    /// - Use of a thread-safety marker without `--preview thread_safety`.
897    pub(crate) fn process_mark_directives(
898        &self,
899        directives_start: u32,
900        directives_len: u32,
901        item_kind_name: &'static str,
902        item_kind: ItemKinds,
903        span: Span,
904    ) -> CompileResult<MarkOutcome> {
905        let mut outcome = MarkOutcome::default();
906        if directives_len == 0 {
907            return Ok(outcome);
908        }
909        let directives = self.rir.get_directives(directives_start, directives_len);
910        for directive in directives.iter() {
911            let dir_name = self.interner.resolve(&directive.name);
912            if dir_name != "mark" {
913                continue;
914            }
915            if directive.args.is_empty() {
916                return Err(CompileError::new(
917                    ErrorKind::UnknownMarker {
918                        name: String::new(),
919                        note: Some(format!(
920                            "`@mark(...)` requires at least one marker name; valid markers: {}",
921                            BUILTIN_MARKERS
922                                .iter()
923                                .map(|m| m.name)
924                                .collect::<Vec<_>>()
925                                .join(", ")
926                        )),
927                    },
928                    directive.span,
929                ));
930            }
931            for arg in &directive.args {
932                let marker_name = self.interner.resolve(arg).to_string();
933                let marker = match get_builtin_marker(&marker_name) {
934                    Some(m) => m,
935                    None => {
936                        let note = marker_diagnosis_note(&marker_name);
937                        return Err(CompileError::new(
938                            ErrorKind::UnknownMarker {
939                                name: marker_name,
940                                note,
941                            },
942                            directive.span,
943                        ));
944                    }
945                };
946                let applicable = match item_kind {
947                    k if k == ItemKinds::STRUCT => marker.applicable_to.includes_struct(),
948                    k if k == ItemKinds::ENUM => marker.applicable_to.includes_enum(),
949                    _ => true,
950                };
951                if !applicable {
952                    return Err(CompileError::new(
953                        ErrorKind::MarkerNotApplicable {
954                            marker: marker.name.to_string(),
955                            item_kind: item_kind_name,
956                        },
957                        directive.span,
958                    ));
959                }
960                match marker.kind {
961                    MarkerKind::Posture(Posture::Copy) => outcome.copy = true,
962                    MarkerKind::Posture(Posture::Affine) => outcome.affine = true,
963                    MarkerKind::Posture(Posture::Linear) => outcome.linear = true,
964                    MarkerKind::ThreadSafety(level) => {
965                        // ADR-0084: thread-safety markers stabilized
966                        // — no preview gate after Phase 7.
967                        if let Some(prev) = outcome.thread_safety_override
968                            && prev != level
969                        {
970                            return Err(CompileError::new(
971                                ErrorKind::ConflictingThreadSafetyMarkers {
972                                    type_name: item_kind_name.to_string(),
973                                },
974                                span,
975                            ));
976                        }
977                        outcome.thread_safety_override = Some(level);
978                    }
979                    MarkerKind::Abi(Abi::C) => {
980                        // ADR-0085 stabilised `@mark(c)` on fns / structs;
981                        // ADR-0086 stabilised it on enums. The marker is
982                        // already known to be applicable to the host item
983                        // kind by this point.
984                        outcome.c_layout = true;
985                    }
986                    MarkerKind::Unchecked => {
987                        // ADR-0088: `@mark(unchecked)` only applies to
988                        // fn-like declarations. The marker registry's
989                        // `applicable_to` field already filters this out
990                        // for struct/enum decls, but if we get here on a
991                        // non-fn item we surface the not-applicable
992                        // error rather than silently dropping it.
993                        return Err(CompileError::new(
994                            ErrorKind::MarkerNotApplicable {
995                                marker: marker.name.to_string(),
996                                item_kind: item_kind_name,
997                            },
998                            directive.span,
999                        ));
1000                    }
1001                }
1002            }
1003        }
1004        // Mutual exclusion across all `@mark` directives on this item.
1005        let posture_count = (outcome.copy as u8) + (outcome.affine as u8) + (outcome.linear as u8);
1006        if posture_count > 1 {
1007            // Reuse the existing `LinearStructCopy` diagnostic spelling for
1008            // Copy + Linear; for the rarer Copy + Affine / Affine + Linear
1009            // combinations we still want a clear error, so we fall back to
1010            // the same kind with a generic name placeholder.
1011            return Err(CompileError::new(
1012                ErrorKind::LinearStructCopy("<conflicting `@mark(...)` markers>".to_string()),
1013                span,
1014            ));
1015        }
1016        Ok(outcome)
1017    }
1018
1019    /// Phase 2: Resolve all declarations.
1020    ///
1021    /// Now that all type names are registered, this resolves:
1022    /// - Struct field types (must be done first for @derive(Copy) validation)
1023    /// - @derive(Copy) struct validation, destructors, functions, and methods
1024    ///
1025    /// # Array Type Registration
1026    ///
1027    /// Array types from explicit type annotations (struct fields, function parameters,
1028    /// return types, local variable annotations) are registered during this phase via
1029    /// `resolve_type()` calls. Array types from literals (inferred during HM inference)
1030    /// are created on-demand via the thread-safe `TypeInternPool` during function
1031    /// body analysis.
1032    pub(crate) fn resolve_declarations(&mut self) -> CompileResult<()> {
1033        // ADR-0075: surface unrecognized `@name` directives before any
1034        // downstream pass that would silently ignore them.
1035        self.validate_directives()?;
1036        // Derives (ADR-0058) are gated by the `comptime_derives` preview
1037        // feature. Reject any `derive` item before we touch the rest of
1038        // declaration gathering so users get a clear diagnostic.
1039        self.validate_derive_decls()?;
1040        // Interfaces (ADR-0056) must be registered before struct/enum field
1041        // and method-param resolution so that:
1042        //   - using an interface name as a struct field type produces a
1043        //     helpful diagnostic redirecting to the comptime path
1044        //   - `comptime T: SomeInterface` bounds are recognized when
1045        //     functions and methods are gathered
1046        //   - `borrow t: SomeInterface` parameter types resolve correctly
1047        self.validate_interface_decls()?;
1048        // ADR-0079: bind `@lang("…")` directives in the prelude to
1049        // interface/enum IDs. Must run after interfaces are registered
1050        // and before any sema pass that consults `lang_items`. Enum
1051        // bindings only resolve once `register_type_names` has populated
1052        // `self.enums`, which already happened in Phase 1.
1053        self.populate_lang_items()?;
1054        self.resolve_struct_fields()?;
1055        self.resolve_enum_variant_fields()?;
1056        // ADR-0080 Phase 3: validate that each named declaration's
1057        // declared posture (`copy` / unmarked / `linear`) is consistent
1058        // with the postures of its fields/variants. Runs after field
1059        // resolution so member types are populated. Anonymous types are
1060        // checked at construction time (see `find_or_create_anon_struct`
1061        // / `find_or_create_anon_enum`).
1062        self.validate_consistency()?;
1063        // ADR-0058 sub-phase: resolve every `@derive(D)` directive on a
1064        // named struct or enum into a binding for the upcoming expansion
1065        // step. Runs after field-type resolution so the host type is
1066        // already fully known.
1067        self.resolve_derive_directives()?;
1068        // ADR-0058 sub-phase: splice every `(host_type, derive_id)`
1069        // binding's methods into the host's method list. Runs after the
1070        // host's fields are known so destructor / Copy validation
1071        // (`resolve_remaining_declarations`) sees the attached methods.
1072        self.expand_derives()?;
1073        self.resolve_remaining_declarations()?;
1074        Ok(())
1075    }
1076
1077    /// Phase 4 of ADR-0058: walk every recorded `DeriveBinding` and splice
1078    /// the named derive's methods into the host type's method list. The
1079    /// same routine is reused at the anonymous-host call site (see
1080    /// `splice_derive_methods_into_struct`).
1081    pub(crate) fn expand_derives(&mut self) -> CompileResult<()> {
1082        // Snapshot the bindings so we can iterate while mutating
1083        // `self.methods`. Bindings are restored after expansion so the
1084        // analysis loop can drive each spliced method's body analysis.
1085        let bindings = self.derive_bindings.clone();
1086        for b in &bindings {
1087            // Anonymous-host expansion uses a different call site; named
1088            // bindings always carry a `host_name` that resolves through
1089            // `self.structs` (or, in the future, `self.enums`).
1090            if b.host_is_enum {
1091                let enum_id = match self.enums.get(&b.host_name).copied() {
1092                    Some(id) => id,
1093                    None => continue,
1094                };
1095                self.splice_derive_methods_into_enum(b.derive_name, enum_id, b.directive_span)?;
1096            } else {
1097                let struct_id = match self.structs.get(&b.host_name).copied() {
1098                    Some(id) => id,
1099                    None => continue,
1100                };
1101                self.splice_derive_methods_into_struct(b.derive_name, struct_id, b.directive_span)?;
1102            }
1103        }
1104        // Replace the (now-consumed) bindings — the cloned working set is
1105        // already done. Phase 4 keeps the original list around for the
1106        // analysis loop to drive body analysis later.
1107        self.derive_bindings = bindings;
1108        Ok(())
1109    }
1110
1111    /// Anonymous-host call site for ADR-0058: walk an anonymous-struct
1112    /// expression's `@derive(...)` directives and splice each derive's
1113    /// methods into the freshly-built host. Should be called exactly once
1114    /// per new anonymous `StructId` (the structural-dedup path skips this).
1115    pub(crate) fn splice_anon_struct_derives(
1116        &mut self,
1117        host_id: StructId,
1118        directives_start: u32,
1119        directives_len: u32,
1120    ) -> CompileResult<()> {
1121        if directives_len == 0 {
1122            return Ok(());
1123        }
1124        let derive_dir_sym = self.interner.get_or_intern("derive");
1125        let directives = self.rir.get_directives(directives_start, directives_len);
1126        for d in directives {
1127            if d.name != derive_dir_sym {
1128                continue;
1129            }
1130            if d.args.len() != 1 {
1131                return Err(CompileError::new(
1132                    ErrorKind::DeriveNotADerive {
1133                        name: "<wrong arg count>".to_string(),
1134                        found: format!("{} arguments", d.args.len()),
1135                    },
1136                    d.span,
1137                ));
1138            }
1139            let derive_name = d.args[0];
1140            // ADR-0059: `@derive(Copy)` on an anonymous host is no-op for
1141            // method splicing; the posture bookkeeping flows through the
1142            // existing copy-directive path.
1143            if self.is_compiler_derive(derive_name) {
1144                continue;
1145            }
1146            let name_str = self.interner.resolve(&derive_name).to_string();
1147            if !self.derives.contains_key(&derive_name) {
1148                let found = if self.structs.contains_key(&derive_name) {
1149                    "struct"
1150                } else if self.enums.contains_key(&derive_name) {
1151                    "enum"
1152                } else if self.interfaces.contains_key(&derive_name) {
1153                    "interface"
1154                } else if self.functions.contains_key(&derive_name) {
1155                    "function"
1156                } else {
1157                    "unknown name"
1158                };
1159                return Err(CompileError::new(
1160                    ErrorKind::DeriveNotADerive {
1161                        name: name_str,
1162                        found: found.to_string(),
1163                    },
1164                    d.span,
1165                ));
1166            }
1167            self.splice_derive_methods_into_struct(derive_name, host_id, d.span)?;
1168        }
1169        Ok(())
1170    }
1171
1172    /// Anonymous-enum mirror of `splice_anon_struct_derives`.
1173    pub(crate) fn splice_anon_enum_derives(
1174        &mut self,
1175        host_id: EnumId,
1176        directives_start: u32,
1177        directives_len: u32,
1178    ) -> CompileResult<()> {
1179        if directives_len == 0 {
1180            return Ok(());
1181        }
1182        let derive_dir_sym = self.interner.get_or_intern("derive");
1183        let directives = self.rir.get_directives(directives_start, directives_len);
1184        for d in directives {
1185            if d.name != derive_dir_sym {
1186                continue;
1187            }
1188            if d.args.len() != 1 {
1189                return Err(CompileError::new(
1190                    ErrorKind::DeriveNotADerive {
1191                        name: "<wrong arg count>".to_string(),
1192                        found: format!("{} arguments", d.args.len()),
1193                    },
1194                    d.span,
1195                ));
1196            }
1197            let derive_name = d.args[0];
1198            // ADR-0059: `@derive(Copy)` short-circuits — no methods to splice.
1199            if self.is_compiler_derive(derive_name) {
1200                continue;
1201            }
1202            let name_str = self.interner.resolve(&derive_name).to_string();
1203            if !self.derives.contains_key(&derive_name) {
1204                let found = if self.structs.contains_key(&derive_name) {
1205                    "struct"
1206                } else if self.enums.contains_key(&derive_name) {
1207                    "enum"
1208                } else if self.interfaces.contains_key(&derive_name) {
1209                    "interface"
1210                } else if self.functions.contains_key(&derive_name) {
1211                    "function"
1212                } else {
1213                    "unknown name"
1214                };
1215                return Err(CompileError::new(
1216                    ErrorKind::DeriveNotADerive {
1217                        name: name_str,
1218                        found: found.to_string(),
1219                    },
1220                    d.span,
1221                ));
1222            }
1223            self.splice_derive_methods_into_enum(derive_name, host_id, d.span)?;
1224        }
1225        Ok(())
1226    }
1227
1228    /// Splice every method of `derive_name` into the struct identified by
1229    /// `host_id`. `Self` is bound to the host struct's `Type`.
1230    pub(crate) fn splice_derive_methods_into_struct(
1231        &mut self,
1232        derive_name: Spur,
1233        host_id: StructId,
1234        directive_span: Span,
1235    ) -> CompileResult<()> {
1236        // Snapshot the per-derive method list (we'll borrow `self` mutably
1237        // when calling `resolve_param_type` below).
1238        let methods: Vec<crate::sema::info::DeriveMethod> = match self.derives.get(&derive_name) {
1239            Some(info) => info.methods.clone(),
1240            None => return Ok(()),
1241        };
1242        let host_type = Type::new_struct(host_id);
1243
1244        // ADR-0079: linear types must not pick up a Clone impl. The
1245        // prelude `derive Clone` synthesizes a fresh `Self` field-by-
1246        // field, which would silently duplicate a linear value. Catch
1247        // this at splice time so the diagnostic points at the
1248        // `@derive(Clone)` directive rather than at the synthesized
1249        // body's first @field_set.
1250        let derive_iface = self.interfaces.get(&derive_name).copied();
1251        if derive_iface.is_some()
1252            && derive_iface == self.lang_items.clone()
1253            && self.type_pool.struct_def(host_id).posture == Posture::Linear
1254        {
1255            let host_str = self.type_pool.struct_def(host_id).name.clone();
1256            return Err(CompileError::new(
1257                ErrorKind::LinearStructClone(host_str),
1258                directive_span,
1259            ));
1260        }
1261
1262        // ADR-0076: bind `Self` to the host struct while resolving derived
1263        // method signatures.
1264        let saved_self = self.current_self.replace(host_type);
1265
1266        let host_file_id = self.type_pool.struct_def(host_id).file_id;
1267        for dm in methods {
1268            let m = self.rir.get(dm.method_ref);
1269            let InstData::FnDecl {
1270                name: method_name,
1271                is_pub: method_is_pub,
1272                is_unchecked,
1273                params_start,
1274                params_len,
1275                return_type,
1276                body,
1277                has_self,
1278                receiver_mode,
1279                ..
1280            } = m.data
1281            else {
1282                continue;
1283            };
1284            let receiver = decode_receiver_mode(receiver_mode);
1285            let key = (host_id, method_name);
1286            if self.methods.contains_key(&key) {
1287                let derive_str = self.interner.resolve(&derive_name).to_string();
1288                let method_str = self.interner.resolve(&method_name).to_string();
1289                let host_str = self.type_pool.struct_def(host_id).name.clone();
1290                let prior_span = self.methods.get(&key).map(|info| info.span);
1291                let mut err = CompileError::new(
1292                    ErrorKind::DuplicateMethod {
1293                        type_name: format!(
1294                            "type `{}` (attached by `@derive({})`)",
1295                            host_str, derive_str
1296                        ),
1297                        method_name: method_str.clone(),
1298                    },
1299                    directive_span,
1300                )
1301                .with_label(
1302                    format!("`{}` declared inside `derive {}`", method_str, derive_str),
1303                    m.span,
1304                );
1305                if let Some(s) = prior_span {
1306                    err = err.with_label(format!("`{}` already attached here", method_str), s);
1307                }
1308                return Err(err);
1309            }
1310
1311            let params = self.rir.get_params(params_start, params_len);
1312            let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
1313            let param_comptime: Vec<bool> = params.iter().map(|p| p.is_comptime).collect();
1314            let mut param_types: Vec<Type> = Vec::with_capacity(params.len());
1315            let mut param_modes: Vec<gruel_rir::RirParamMode> = Vec::with_capacity(params.len());
1316            for p in &params {
1317                let (ty, mode) = self.resolve_param_type(p.ty, p.mode, m.span)?;
1318                param_types.push(ty);
1319                param_modes.push(mode);
1320            }
1321            let ret_type = self.resolve_type(return_type, m.span)?;
1322            let param_range =
1323                self.param_arena
1324                    .alloc(param_names, param_types, param_modes, param_comptime);
1325
1326            // ADR-0073: derived methods are scoped to the host's module —
1327            // the host's file_id is the relevant target_file_id for any
1328            // visibility check at a call site.
1329            self.methods.insert(
1330                key,
1331                MethodInfo {
1332                    struct_type: host_type,
1333                    has_self,
1334                    receiver,
1335                    params: param_range,
1336                    return_type: ret_type,
1337                    body,
1338                    span: m.span,
1339                    is_unchecked,
1340                    is_generic: false,
1341                    return_type_sym: return_type,
1342                    is_pub: method_is_pub,
1343                    file_id: host_file_id,
1344                },
1345            );
1346        }
1347        self.current_self = saved_self;
1348        Ok(())
1349    }
1350
1351    /// Splice every method of `derive_name` into the enum identified by
1352    /// `host_id`. `Self` is bound to the host enum's `Type`. Mirrors the
1353    /// struct case; structs and enums share the splicing logic (only the
1354    /// destination map differs).
1355    pub(crate) fn splice_derive_methods_into_enum(
1356        &mut self,
1357        derive_name: Spur,
1358        host_id: EnumId,
1359        directive_span: Span,
1360    ) -> CompileResult<()> {
1361        let methods: Vec<crate::sema::info::DeriveMethod> = match self.derives.get(&derive_name) {
1362            Some(info) => info.methods.clone(),
1363            None => return Ok(()),
1364        };
1365        let host_type = Type::new_enum(host_id);
1366        // ADR-0076: bind `Self` to the host enum while resolving derived
1367        // method signatures.
1368        let saved_self = self.current_self.replace(host_type);
1369        let host_file_id = self.type_pool.enum_def(host_id).file_id;
1370
1371        for dm in methods {
1372            let m = self.rir.get(dm.method_ref);
1373            let InstData::FnDecl {
1374                name: method_name,
1375                is_pub: method_is_pub,
1376                is_unchecked,
1377                params_start,
1378                params_len,
1379                return_type,
1380                body,
1381                has_self,
1382                receiver_mode,
1383                ..
1384            } = m.data
1385            else {
1386                continue;
1387            };
1388            let receiver = decode_receiver_mode(receiver_mode);
1389            let key = (host_id, method_name);
1390            if self.enum_methods.contains_key(&key) {
1391                let derive_str = self.interner.resolve(&derive_name).to_string();
1392                let method_str = self.interner.resolve(&method_name).to_string();
1393                let prior_span = self.enum_methods.get(&key).map(|info| info.span);
1394                let mut err = CompileError::new(
1395                    ErrorKind::DuplicateMethod {
1396                        type_name: format!("enum (attached by `@derive({})`)", derive_str),
1397                        method_name: method_str.clone(),
1398                    },
1399                    directive_span,
1400                )
1401                .with_label(
1402                    format!("`{}` declared inside `derive {}`", method_str, derive_str),
1403                    m.span,
1404                );
1405                if let Some(s) = prior_span {
1406                    err = err.with_label(format!("`{}` already attached here", method_str), s);
1407                }
1408                return Err(err);
1409            }
1410
1411            let params = self.rir.get_params(params_start, params_len);
1412            let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
1413            let param_comptime: Vec<bool> = params.iter().map(|p| p.is_comptime).collect();
1414            let mut param_types: Vec<Type> = Vec::with_capacity(params.len());
1415            let mut param_modes: Vec<gruel_rir::RirParamMode> = Vec::with_capacity(params.len());
1416            for p in &params {
1417                let (ty, mode) = self.resolve_param_type(p.ty, p.mode, m.span)?;
1418                param_types.push(ty);
1419                param_modes.push(mode);
1420            }
1421            let ret_type = self.resolve_type(return_type, m.span)?;
1422            let param_range =
1423                self.param_arena
1424                    .alloc(param_names, param_types, param_modes, param_comptime);
1425
1426            self.enum_methods.insert(
1427                key,
1428                MethodInfo {
1429                    struct_type: host_type,
1430                    has_self,
1431                    receiver,
1432                    params: param_range,
1433                    return_type: ret_type,
1434                    body,
1435                    span: m.span,
1436                    is_unchecked,
1437                    is_generic: false,
1438                    return_type_sym: return_type,
1439                    is_pub: method_is_pub,
1440                    file_id: host_file_id,
1441                },
1442            );
1443        }
1444        self.current_self = saved_self;
1445        Ok(())
1446    }
1447
1448    /// Walk every named struct/enum declaration; for each `@derive(D)`
1449    /// directive, resolve `D` against `Sema::derives` and record a
1450    /// `DeriveBinding` for Phase 4. Errors if `D` doesn't name a `derive`
1451    /// item.
1452    pub(crate) fn resolve_derive_directives(&mut self) -> CompileResult<()> {
1453        use super::DeriveBinding;
1454
1455        // Snapshot of struct/enum declarations carrying `@derive(...)`.
1456        struct RawAttach {
1457            host_name: Spur,
1458            host_is_enum: bool,
1459            host_span: Span,
1460            derive_names: Vec<(Spur, Span)>,
1461        }
1462        let derive_dir_sym = self.interner.get_or_intern("derive");
1463        let mut raw: Vec<RawAttach> = Vec::new();
1464
1465        for (_, inst) in self.rir.iter() {
1466            // ADR-0079 Phase 1 routed directives through both
1467            // `StructDecl` and `EnumDecl`; collect from both so
1468            // `@derive(...)` works on enums too.
1469            let (directives_start, directives_len, name, host_is_enum) = match &inst.data {
1470                InstData::StructDecl {
1471                    directives_start,
1472                    directives_len,
1473                    name,
1474                    ..
1475                } => (*directives_start, *directives_len, *name, false),
1476                InstData::EnumDecl {
1477                    directives_start,
1478                    directives_len,
1479                    name,
1480                    ..
1481                } => (*directives_start, *directives_len, *name, true),
1482                _ => continue,
1483            };
1484            if directives_len == 0 {
1485                continue;
1486            }
1487            let directives = self.rir.get_directives(directives_start, directives_len);
1488            let mut attached = Vec::new();
1489            for d in &directives {
1490                if d.name == derive_dir_sym {
1491                    if d.args.len() != 1 {
1492                        return Err(CompileError::new(
1493                            ErrorKind::DeriveNotADerive {
1494                                name: "<wrong arg count>".to_string(),
1495                                found: format!("{} arguments", d.args.len()),
1496                            },
1497                            d.span,
1498                        ));
1499                    }
1500                    attached.push((d.args[0], d.span));
1501                }
1502            }
1503            if !attached.is_empty() {
1504                raw.push(RawAttach {
1505                    host_name: name,
1506                    host_is_enum,
1507                    host_span: inst.span,
1508                    derive_names: attached,
1509                });
1510            }
1511        }
1512
1513        // Resolve each binding.
1514        for r in raw {
1515            for (derive_name, dir_span) in r.derive_names {
1516                // `@derive(Copy)` is compiler-recognized (ADR-0059): the
1517                // field-Copy validation already runs via the posture
1518                // bookkeeping, and there is no derive item to look up. Skip
1519                // the regular resolution path.
1520                if self.is_compiler_derive(derive_name) {
1521                    continue;
1522                }
1523
1524                let name_str = self.interner.resolve(&derive_name).to_string();
1525
1526                // Resolution order: a `derive` item, else categorize what
1527                // we actually found for a clearer diagnostic.
1528                if !self.derives.contains_key(&derive_name) {
1529                    let found = if self.structs.contains_key(&derive_name) {
1530                        "struct"
1531                    } else if self.enums.contains_key(&derive_name) {
1532                        "enum"
1533                    } else if self.interfaces.contains_key(&derive_name) {
1534                        "interface"
1535                    } else if self.functions.contains_key(&derive_name) {
1536                        "function"
1537                    } else {
1538                        "unknown name"
1539                    };
1540                    return Err(CompileError::new(
1541                        ErrorKind::DeriveNotADerive {
1542                            name: name_str,
1543                            found: found.to_string(),
1544                        },
1545                        dir_span,
1546                    ));
1547                }
1548
1549                self.derive_bindings.push(DeriveBinding {
1550                    host_name: r.host_name,
1551                    host_is_enum: r.host_is_enum,
1552                    derive_name,
1553                    host_span: r.host_span,
1554                    directive_span: dir_span,
1555                });
1556            }
1557        }
1558
1559        Ok(())
1560    }
1561
1562    /// ADR-0059 / ADR-0079: a name was previously recognized as a
1563    /// compiler-handled derive (`Copy` was the only such name).
1564    /// ADR-0080 retired `Copy` from the interface registry, so this
1565    /// hook always returns `false` and `@derive(...)` resolution
1566    /// flows entirely through the standard derive-resolution path —
1567    /// `@derive(Copy)` falls through the existing "unknown interface"
1568    /// diagnostic, exactly as the ADR specified.
1569    fn is_compiler_derive(&self, _name: Spur) -> bool {
1570        false
1571    }
1572
1573    /// ADR-0058 phases 1 + 2: gate `derive` items behind the
1574    /// `comptime_derives` preview feature, register each into
1575    /// `Sema::derives` for later expansion, and reject ill-formed bodies
1576    /// (duplicate names, name collisions with structs/enums/interfaces,
1577    /// duplicate methods within a single derive, and direct `self.field`
1578    /// projection — the host type's structure isn't known at
1579    /// derive-definition time).
1580    pub(crate) fn validate_derive_decls(&mut self) -> CompileResult<()> {
1581        use super::DeriveInfo;
1582        use crate::sema::info::DeriveMethod;
1583
1584        // Snapshot: copy out enough RIR data so we can mutate `self.derives`
1585        // and emit diagnostics without holding shared borrows on `self.rir`.
1586        struct RawDerive {
1587            name: Spur,
1588            decl_ref: gruel_rir::InstRef,
1589            span: Span,
1590            methods_start: u32,
1591            methods_len: u32,
1592        }
1593        let mut raw: Vec<RawDerive> = Vec::new();
1594        for (inst_ref, inst) in self.rir.iter() {
1595            if let InstData::DeriveDecl {
1596                name,
1597                methods_start,
1598                methods_len,
1599            } = &inst.data
1600            {
1601                raw.push(RawDerive {
1602                    name: *name,
1603                    decl_ref: inst_ref,
1604                    span: inst.span,
1605                    methods_start: *methods_start,
1606                    methods_len: *methods_len,
1607                });
1608            }
1609        }
1610
1611        for d in raw {
1612            let name_str = self.interner.resolve(&d.name).to_string();
1613
1614            // ADR-0079: a derive may share its name with an interface so the
1615            // prelude can ship `derive Clone {...}` alongside
1616            // `interface Clone {...}` — the derive provides an implementation
1617            // for that interface. Other collisions (struct, enum, another
1618            // derive) remain rejected.
1619            if self.derives.contains_key(&d.name)
1620                || self.structs.contains_key(&d.name)
1621                || self.enums.contains_key(&d.name)
1622            {
1623                return Err(CompileError::new(
1624                    ErrorKind::DuplicateTypeDefinition {
1625                        type_name: format!("derive `{}`", name_str),
1626                    },
1627                    d.span,
1628                ));
1629            }
1630
1631            let method_refs = self.rir.get_inst_refs(d.methods_start, d.methods_len);
1632            let mut seen: HashSet<Spur> = HashSet::default();
1633            let mut methods: Vec<DeriveMethod> = Vec::with_capacity(method_refs.len());
1634            for method_ref in method_refs {
1635                let m = self.rir.get(method_ref);
1636                let InstData::FnDecl {
1637                    name: method_name,
1638                    has_self,
1639                    ..
1640                } = &m.data
1641                else {
1642                    // The grammar only admits method declarations inside a
1643                    // derive body, but if RIR ever produces something else
1644                    // we surface a clear diagnostic instead of panicking.
1645                    return Err(CompileError::new(
1646                        ErrorKind::InternalError(format!(
1647                            "non-method instruction inside `derive {}` body",
1648                            name_str
1649                        )),
1650                        m.span,
1651                    ));
1652                };
1653
1654                if !seen.insert(*method_name) {
1655                    return Err(CompileError::new(
1656                        ErrorKind::DuplicateMethod {
1657                            type_name: format!("derive `{}`", name_str),
1658                            method_name: self.interner.resolve(method_name).to_string(),
1659                        },
1660                        m.span,
1661                    ));
1662                }
1663
1664                self.reject_direct_self_projection(*method_name, &name_str, method_ref)?;
1665
1666                methods.push(DeriveMethod {
1667                    name: *method_name,
1668                    has_self: *has_self,
1669                    method_ref,
1670                    span: m.span,
1671                });
1672            }
1673
1674            self.derives.insert(
1675                d.name,
1676                DeriveInfo {
1677                    name: d.name,
1678                    decl_ref: d.decl_ref,
1679                    span: d.span,
1680                    methods,
1681                },
1682            );
1683        }
1684
1685        Ok(())
1686    }
1687
1688    /// Walk the RIR range `[body_start, decl)` for the method whose `FnDecl`
1689    /// is at `method_ref`, and reject any `FieldGet { base: VarRef "self" }`.
1690    /// The host's structure isn't known at derive-definition time, so direct
1691    /// projection (`self.x`) is illegal — users must go through
1692    /// `@field(self, "x")` (ADR-0058).
1693    fn reject_direct_self_projection(
1694        &self,
1695        method_name: Spur,
1696        derive_name: &str,
1697        method_ref: gruel_rir::InstRef,
1698    ) -> CompileResult<()> {
1699        let self_sym = match self.interner.get("self") {
1700            Some(s) => s,
1701            // No `self` symbol exists yet, so no method can reference it.
1702            None => return Ok(()),
1703        };
1704
1705        // Find this method's function span so we can iterate just its body.
1706        let span = match self
1707            .rir
1708            .function_spans()
1709            .iter()
1710            .find(|s| s.decl == method_ref)
1711        {
1712            Some(s) => s,
1713            None => return Ok(()),
1714        };
1715
1716        let view = self.rir.function_view(span);
1717        for (_, inst) in view.iter() {
1718            if let InstData::FieldGet { base, .. } = &inst.data {
1719                let base_inst = self.rir.get(*base);
1720                if let InstData::VarRef { name } = &base_inst.data
1721                    && *name == self_sym
1722                {
1723                    return Err(CompileError::new(
1724                        ErrorKind::DeriveDirectFieldAccess {
1725                            derive_name: derive_name.to_string(),
1726                            method_name: self.interner.resolve(&method_name).to_string(),
1727                        },
1728                        inst.span,
1729                    ));
1730                }
1731            }
1732        }
1733        Ok(())
1734    }
1735
1736    /// Resolve enum variant field types. Must run after all type names are registered
1737    /// so that field types can reference other enums/structs.
1738    pub(crate) fn resolve_enum_variant_fields(&mut self) -> CompileResult<()> {
1739        for (_, inst) in self.rir.iter() {
1740            if let InstData::EnumDecl {
1741                name,
1742                variants_start,
1743                variants_len,
1744                ..
1745            } = &inst.data
1746            {
1747                let enum_id = match self.enums.get(name) {
1748                    Some(id) => *id,
1749                    None => continue, // not registered (shouldn't happen)
1750                };
1751
1752                let raw_variants = self
1753                    .rir
1754                    .get_enum_variant_decls(*variants_start, *variants_len);
1755                let has_data = raw_variants.iter().any(|(_, fields, _)| !fields.is_empty());
1756                if !has_data {
1757                    continue; // unit-only enum, no field types to resolve
1758                }
1759
1760                let mut resolved_variants = Vec::with_capacity(raw_variants.len());
1761                for (vname, field_type_spurs, field_name_spurs) in &raw_variants {
1762                    let mut resolved_fields = Vec::with_capacity(field_type_spurs.len());
1763                    for field_ty_spur in field_type_spurs {
1764                        let field_ty = self.resolve_type(*field_ty_spur, inst.span)?;
1765                        resolved_fields.push(field_ty);
1766                    }
1767                    let field_names: Vec<String> = field_name_spurs
1768                        .iter()
1769                        .map(|n| self.interner.resolve(n).to_string())
1770                        .collect();
1771                    resolved_variants.push(EnumVariantDef {
1772                        name: self.interner.resolve(vname).to_string(),
1773                        fields: resolved_fields,
1774                        field_names,
1775                    });
1776                }
1777
1778                let mut enum_def = self.type_pool.enum_def(enum_id);
1779                enum_def.variants = resolved_variants;
1780                self.type_pool.update_enum_def(enum_id, enum_def);
1781            }
1782        }
1783        Ok(())
1784    }
1785
1786    /// ADR-0080 Phase 3: posture-consistency walker.
1787    ///
1788    /// Runs after `resolve_struct_fields` and `resolve_enum_variant_fields`
1789    /// have populated every field/variant `Type`, but before the rest of
1790    /// `resolve_remaining_declarations`. For every named struct or enum
1791    /// declaration, it classifies each member's posture (Copy / Affine /
1792    /// Linear) and folds the result into the propagated posture, then
1793    /// compares against the declared posture from the keyword:
1794    ///
1795    /// | Declared    | Rule                                   |
1796    /// |-------------|----------------------------------------|
1797    /// | `copy`      | every member must be Copy              |
1798    /// | (unmarked)  | no member may be Linear                |
1799    /// | `linear`    | (no constraint — linear holds anything)|
1800    ///
1801    /// On mismatch the error span is the host declaration's span and the
1802    /// message names the offending member's type and posture (richer
1803    /// per-field spans land when struct/enum field positions become
1804    /// addressable from `StructDef` / `EnumDef`).
1805    ///
1806    /// The walker is one function rather than a struct-validator + enum-
1807    /// validator pair because the only difference between the two cases is
1808    /// how members are enumerated; the posture-folding logic is identical.
1809    ///
1810    /// ADR-0084: in addition to computing posture, this pass also computes
1811    /// the structural minimum thread-safety classification over members
1812    /// and applies any `@mark(unsend)` / `@mark(checked_send)` /
1813    /// `@mark(checked_sync)` override recorded in
1814    /// `mark_thread_safety_decls`. The function was renamed from
1815    /// `validate_posture_consistency` when the thread-safety axis was
1816    /// added.
1817    pub(crate) fn validate_consistency(&mut self) -> CompileResult<()> {
1818        // ADR-0083 Phase 1: instead of just validating declared posture
1819        // against members, we run *uniform structural inference* on every
1820        // named struct/enum. The result reconciles with whatever the user
1821        // declared via the `copy`/`linear` keyword (ADR-0080) or
1822        // `@mark(...)` directive (ADR-0083):
1823        //
1824        // - declared Linear (or `@mark(linear)`) → final is Linear (no
1825        //   member constraint).
1826        // - declared Copy (or `@mark(copy)`) → every member must be Copy;
1827        //   final is Copy.
1828        // - declared Affine via `@mark(affine)` → no member may be
1829        //   Linear; final is Affine (suppresses Copy inference).
1830        // - no declaration → final posture is the inferred posture
1831        //   (NEW: previously named types were Affine by default; now
1832        //   structs/enums of all-Copy members infer Copy).
1833        //
1834        // ADR-0080 carve-out: types with `fn __drop(self)` are never Copy
1835        // (Copy ⊥ Drop). We pre-scan RIR for both inline `fn __drop` methods
1836        // and top-level `drop fn TypeName(self)` declarations and downgrade
1837        // Copy → Affine when a destructor is present.
1838        //
1839        // Member-postures are classified via `classify_posture`, which
1840        // already handles arrays/tuples/Vec/Option/Result transparently.
1841        let types_with_drop = self.collect_types_with_drop();
1842        for (_, inst) in self.rir.iter() {
1843            match &inst.data {
1844                InstData::StructDecl {
1845                    name,
1846                    methods_start,
1847                    methods_len,
1848                    ..
1849                } => {
1850                    let Some(&struct_id) = self.structs.get(name) else {
1851                        continue;
1852                    };
1853                    let def = self.type_pool.struct_def(struct_id);
1854                    let declared_affine = self.mark_affine_decls.contains(name);
1855                    let declared = match def.posture {
1856                        Posture::Linear => DeclaredPosture::Linear,
1857                        Posture::Copy => DeclaredPosture::Copy,
1858                        Posture::Affine if declared_affine => DeclaredPosture::Affine,
1859                        Posture::Affine => DeclaredPosture::Unmarked,
1860                    };
1861                    let host_name = def.name.clone();
1862                    let mut any_linear = false;
1863                    let mut all_copy = true;
1864                    for field in &def.fields {
1865                        let posture = self.classify_posture(field.ty);
1866                        match posture {
1867                            MemberPosture::Linear => any_linear = true,
1868                            MemberPosture::Affine => all_copy = false,
1869                            MemberPosture::Copy => {}
1870                        }
1871                        if let Some(err) = check_posture_against_declared(
1872                            "struct",
1873                            host_name.as_str(),
1874                            declared,
1875                            "field",
1876                            &field.name,
1877                            self.format_type_name(field.ty).as_str(),
1878                            posture,
1879                            inst.span,
1880                        ) {
1881                            return Err(err);
1882                        }
1883                    }
1884                    let has_drop = self.has_inline_drop_method(*methods_start, *methods_len)
1885                        || types_with_drop.contains(name);
1886                    let inferred = if any_linear {
1887                        MemberPosture::Linear
1888                    } else if all_copy && !has_drop {
1889                        MemberPosture::Copy
1890                    } else {
1891                        MemberPosture::Affine
1892                    };
1893                    // ADR-0084: structural minimum over fields. Empty
1894                    // composites fold to `Sync`, the identity element.
1895                    let inferred_thread_safety = self
1896                        .type_pool
1897                        .struct_def(struct_id)
1898                        .fields
1899                        .iter()
1900                        .map(|f| self.type_pool.is_thread_safety_type(f.ty))
1901                        .min()
1902                        .unwrap_or(ThreadSafety::Sync);
1903                    let thread_safety_override = self.mark_thread_safety_decls.get(name).copied();
1904                    self.apply_inferred_struct_posture(struct_id, declared, inferred);
1905                    self.apply_thread_safety_struct(
1906                        struct_id,
1907                        inferred_thread_safety,
1908                        thread_safety_override,
1909                    );
1910                }
1911                InstData::EnumDecl {
1912                    name,
1913                    methods_start,
1914                    methods_len,
1915                    ..
1916                } => {
1917                    let Some(&enum_id) = self.enums.get(name) else {
1918                        continue;
1919                    };
1920                    let def = self.type_pool.enum_def(enum_id);
1921                    let declared_affine = self.mark_affine_decls.contains(name);
1922                    let declared = match def.posture {
1923                        Posture::Linear => DeclaredPosture::Linear,
1924                        Posture::Copy => DeclaredPosture::Copy,
1925                        Posture::Affine if declared_affine => DeclaredPosture::Affine,
1926                        Posture::Affine => DeclaredPosture::Unmarked,
1927                    };
1928                    let host_name = def.name.clone();
1929                    let mut any_linear = false;
1930                    let mut all_copy = true;
1931                    for variant in &def.variants {
1932                        for (i, field_ty) in variant.fields.iter().enumerate() {
1933                            let member_name = if let Some(name) = variant.field_names.get(i) {
1934                                format!("{}::{}.{}", host_name, variant.name, name)
1935                            } else {
1936                                format!("{}::{}.{}", host_name, variant.name, i)
1937                            };
1938                            let posture = self.classify_posture(*field_ty);
1939                            match posture {
1940                                MemberPosture::Linear => any_linear = true,
1941                                MemberPosture::Affine => all_copy = false,
1942                                MemberPosture::Copy => {}
1943                            }
1944                            if let Some(err) = check_posture_against_declared(
1945                                "enum",
1946                                host_name.as_str(),
1947                                declared,
1948                                "variant field",
1949                                &member_name,
1950                                self.format_type_name(*field_ty).as_str(),
1951                                posture,
1952                                inst.span,
1953                            ) {
1954                                return Err(err);
1955                            }
1956                        }
1957                    }
1958                    let has_drop = self.has_inline_drop_method(*methods_start, *methods_len)
1959                        || types_with_drop.contains(name);
1960                    let inferred = if any_linear {
1961                        MemberPosture::Linear
1962                    } else if all_copy && !has_drop {
1963                        MemberPosture::Copy
1964                    } else {
1965                        MemberPosture::Affine
1966                    };
1967                    // ADR-0084: structural minimum across every variant
1968                    // payload field. Mirrors the struct branch above.
1969                    let inferred_thread_safety = self
1970                        .type_pool
1971                        .enum_def(enum_id)
1972                        .variants
1973                        .iter()
1974                        .flat_map(|v| v.fields.iter().copied())
1975                        .map(|f| self.type_pool.is_thread_safety_type(f))
1976                        .min()
1977                        .unwrap_or(ThreadSafety::Sync);
1978                    let thread_safety_override = self.mark_thread_safety_decls.get(name).copied();
1979                    self.apply_inferred_enum_posture(enum_id, declared, inferred);
1980                    self.apply_thread_safety_enum(
1981                        enum_id,
1982                        inferred_thread_safety,
1983                        thread_safety_override,
1984                    );
1985                }
1986                _ => {}
1987            }
1988        }
1989        Ok(())
1990    }
1991
1992    /// ADR-0083 (revised): destructors are `fn __drop(self)` methods
1993    /// declared inside the struct body. The retired top-level
1994    /// `drop fn TypeName(self)` form had its own collection pass; with
1995    /// it gone, only `has_inline_drop_method` remains.
1996    fn collect_types_with_drop(&self) -> HashSet<Spur> {
1997        // No top-level drop fns; inline `fn __drop(self)` is detected via
1998        // `has_inline_drop_method` at the per-decl site.
1999        HashSet::default()
2000    }
2001
2002    /// ADR-0080 / ADR-0083: detect whether a struct/enum's inline method
2003    /// list contains a `fn __drop(self)` declaration.
2004    fn has_inline_drop_method(&self, methods_start: u32, methods_len: u32) -> bool {
2005        if methods_len == 0 {
2006            return false;
2007        }
2008        let drop_name = self.interner.get("__drop");
2009        let Some(drop_name) = drop_name else {
2010            return false;
2011        };
2012        let method_refs = self.rir.get_inst_refs(methods_start, methods_len);
2013        for method_ref in method_refs {
2014            let method_inst = self.rir.get(method_ref);
2015            if let InstData::FnDecl { name, has_self, .. } = &method_inst.data
2016                && *name == drop_name
2017                && *has_self
2018            {
2019                return true;
2020            }
2021        }
2022        false
2023    }
2024
2025    /// ADR-0083 Phase 1: write the final posture into the struct's
2026    /// `posture` field. If the user declared the posture (`copy struct`
2027    /// / `linear struct` / `@mark(...)`), the field is already set in
2028    /// `register_type_names`; we only need to fill it in for unmarked
2029    /// types from the inferred posture.
2030    fn apply_inferred_struct_posture(
2031        &mut self,
2032        struct_id: StructId,
2033        declared: DeclaredPosture,
2034        inferred: MemberPosture,
2035    ) {
2036        if declared != DeclaredPosture::Unmarked {
2037            return;
2038        }
2039        let mut def = self.type_pool.struct_def(struct_id);
2040        def.posture = match inferred {
2041            MemberPosture::Copy => Posture::Copy,
2042            MemberPosture::Affine => Posture::Affine,
2043            MemberPosture::Linear => Posture::Linear,
2044        };
2045        self.type_pool.update_struct_def(struct_id, def);
2046    }
2047
2048    fn apply_inferred_enum_posture(
2049        &mut self,
2050        enum_id: EnumId,
2051        declared: DeclaredPosture,
2052        inferred: MemberPosture,
2053    ) {
2054        if declared != DeclaredPosture::Unmarked {
2055            return;
2056        }
2057        let mut def = self.type_pool.enum_def(enum_id);
2058        def.posture = match inferred {
2059            MemberPosture::Copy => Posture::Copy,
2060            MemberPosture::Affine => Posture::Affine,
2061            MemberPosture::Linear => Posture::Linear,
2062        };
2063        self.type_pool.update_enum_def(enum_id, def);
2064    }
2065
2066    /// ADR-0084: write the final thread-safety classification on the
2067    /// struct's def. The user override (if any) wins, even when it
2068    /// contradicts the structurally-inferred value — that's the whole
2069    /// point of `@mark(checked_send)` / `@mark(checked_sync)`. The
2070    /// `unsend` marker is also accepted regardless of inference (always
2071    /// safe to restrict).
2072    fn apply_thread_safety_struct(
2073        &self,
2074        struct_id: StructId,
2075        inferred: ThreadSafety,
2076        override_: Option<ThreadSafety>,
2077    ) {
2078        let mut def = self.type_pool.struct_def(struct_id);
2079        def.thread_safety = override_.unwrap_or(inferred);
2080        self.type_pool.update_struct_def(struct_id, def);
2081    }
2082
2083    fn apply_thread_safety_enum(
2084        &self,
2085        enum_id: EnumId,
2086        inferred: ThreadSafety,
2087        override_: Option<ThreadSafety>,
2088    ) {
2089        let mut def = self.type_pool.enum_def(enum_id);
2090        def.thread_safety = override_.unwrap_or(inferred);
2091        self.type_pool.update_enum_def(enum_id, def);
2092    }
2093
2094    /// Classify a type's ownership posture (ADR-0080).
2095    fn classify_posture(&self, ty: crate::types::Type) -> MemberPosture {
2096        if self.is_type_linear(ty) {
2097            MemberPosture::Linear
2098        } else if self.is_type_copy(ty) {
2099            MemberPosture::Copy
2100        } else {
2101            MemberPosture::Affine
2102        }
2103    }
2104
2105    // ADR-0080 helper end.
2106
2107    /// Resolve struct field types. Must run before @derive(Copy) validation.
2108    pub(crate) fn resolve_struct_fields(&mut self) -> CompileResult<()> {
2109        for (_, inst) in self.rir.iter() {
2110            if let InstData::StructDecl {
2111                name,
2112                fields_start,
2113                fields_len,
2114                ..
2115            } = &inst.data
2116            {
2117                let name_str = self.interner.resolve(name).to_string();
2118                // Verify the struct exists in our lookup table
2119                if !self.structs.contains_key(name) {
2120                    return Err(CompileError::new(
2121                        ErrorKind::InternalError(
2122                            ice!(
2123                                "struct not found in struct map",
2124                                phase: "sema/declarations",
2125                                details: {
2126                                    "struct_name" => name_str.to_string()
2127                                }
2128                            )
2129                            .to_string(),
2130                        ),
2131                        inst.span,
2132                    ));
2133                }
2134
2135                // Get the struct ID from the lookup table
2136                let struct_id = *self.structs.get(name).ok_or_else(|| {
2137                    CompileError::new(
2138                        ErrorKind::InternalError(
2139                            ice!(
2140                                "struct not found in structs map",
2141                                phase: "sema/declarations",
2142                                details: {
2143                                    "struct_name" => name_str.to_string()
2144                                }
2145                            )
2146                            .to_string(),
2147                        ),
2148                        inst.span,
2149                    )
2150                })?;
2151
2152                let struct_name = name_str.clone();
2153                let fields = self
2154                    .rir
2155                    .get_field_decls_with_vis(*fields_start, *fields_len);
2156
2157                // Check for duplicate field names
2158                let mut seen_fields: HashSet<Spur> = HashSet::default();
2159                for (field_name, _, _) in &fields {
2160                    if !seen_fields.insert(*field_name) {
2161                        let field_name_str = self.interner.resolve(field_name).to_string();
2162                        return Err(CompileError::new(
2163                            ErrorKind::DuplicateField {
2164                                struct_name,
2165                                field_name: field_name_str,
2166                            },
2167                            inst.span,
2168                        ));
2169                    }
2170                }
2171
2172                // Resolve field types
2173                let mut resolved_fields = Vec::new();
2174                for (field_name, field_type, is_pub) in &fields {
2175                    let field_ty = self.resolve_type(*field_type, inst.span)?;
2176                    resolved_fields.push(StructField {
2177                        name: self.interner.resolve(field_name).to_string(),
2178                        ty: field_ty,
2179
2180                        is_pub: *is_pub,
2181                    });
2182                }
2183
2184                // Update the struct definition in the pool with resolved fields
2185                let mut struct_def = self.type_pool.struct_def(struct_id);
2186                // ADR-0085: validate `@mark(c)` struct fields recursively.
2187                // Any non-C field on a C-layout struct is rejected.
2188                if struct_def.is_c_layout {
2189                    for f in &resolved_fields {
2190                        self.validate_ffi_type(
2191                            f.ty,
2192                            &format!(
2193                                "as the type of field `{}` on `@mark(c) struct {}`",
2194                                f.name, struct_def.name
2195                            ),
2196                            inst.span,
2197                        )?;
2198                    }
2199                }
2200                struct_def.fields = resolved_fields;
2201                self.type_pool.update_struct_def(struct_id, struct_def);
2202            }
2203        }
2204        Ok(())
2205    }
2206
2207    /// Resolve @derive(Copy) validation, destructors, functions, and methods.
2208    pub(crate) fn resolve_remaining_declarations(&mut self) -> CompileResult<()> {
2209        // Collect all method InstRefs that should NOT be processed as
2210        // top-level functions:
2211        //
2212        // - Anonymous struct/enum methods are registered later during
2213        //   comptime evaluation with proper Self resolution.
2214        // - `derive` body methods (ADR-0058) are spliced into a host type
2215        //   at expansion time.
2216        // - Named struct/enum methods (including associated functions like
2217        //   `fn new() -> Self`) are collected via `collect_struct_methods` /
2218        //   `collect_enum_methods` with `current_self` set per ADR-0076.
2219        let mut anon_type_method_refs = rustc_hash::FxHashSet::default();
2220        for (_, inst) in self.rir.iter() {
2221            let (methods_start, methods_len) = match &inst.data {
2222                InstData::AnonStructType {
2223                    methods_start,
2224                    methods_len,
2225                    ..
2226                } => (*methods_start, *methods_len),
2227                InstData::AnonEnumType {
2228                    methods_start,
2229                    methods_len,
2230                    ..
2231                } => (*methods_start, *methods_len),
2232                InstData::DeriveDecl {
2233                    methods_start,
2234                    methods_len,
2235                    ..
2236                } => (*methods_start, *methods_len),
2237                InstData::StructDecl {
2238                    methods_start,
2239                    methods_len,
2240                    ..
2241                } => (*methods_start, *methods_len),
2242                InstData::EnumDecl {
2243                    methods_start,
2244                    methods_len,
2245                    ..
2246                } => (*methods_start, *methods_len),
2247                _ => continue,
2248            };
2249            let method_refs = self.rir.get_inst_refs(methods_start, methods_len);
2250            for method_ref in method_refs {
2251                anon_type_method_refs.insert(method_ref);
2252            }
2253        }
2254
2255        // First pass: collect all declarations and validate @derive(Copy) structs
2256        for (inst_ref, inst) in self.rir.iter() {
2257            match &inst.data {
2258                InstData::StructDecl {
2259                    directives_start,
2260                    directives_len,
2261                    name,
2262                    methods_start,
2263                    methods_len,
2264                    ..
2265                } => {
2266                    // ADR-0079: `validate_copy_struct` and
2267                    // `validate_clone_struct` are both retired. The
2268                    // prelude `derive Copy` / `derive Clone` bodies
2269                    // express the field-Copy / field-Clone
2270                    // invariants in Gruel via
2271                    // `comptime_unroll for f in @type_info(Self).fields`
2272                    // + `comptime if (!@implements(f.field_type, …))`
2273                    // + `@compile_error`. Linearity is enforced by
2274                    // the structural Copy/Clone conformance checks.
2275                    let _ = (*directives_start, *directives_len);
2276                    // Collect methods defined inline in the struct
2277                    self.collect_struct_methods(*name, *methods_start, *methods_len, inst.span)?;
2278                }
2279
2280                InstData::EnumDecl {
2281                    name,
2282                    methods_start,
2283                    methods_len,
2284                    ..
2285                } => {
2286                    // Collect methods defined inline in the enum (ADR-0053).
2287                    self.collect_enum_methods(*name, *methods_start, *methods_len, inst.span)?;
2288                }
2289
2290                InstData::FnDecl {
2291                    directives_start,
2292                    directives_len,
2293                    is_pub,
2294                    is_unchecked,
2295                    name,
2296                    params_start,
2297                    params_len,
2298                    return_type,
2299                    body,
2300                    has_self,
2301                    ..
2302                } => {
2303                    // Skip methods (has_self = true) - these are handled elsewhere:
2304                    // - Named struct methods are collected via ImplDecl
2305                    if *has_self {
2306                        continue;
2307                    }
2308
2309                    // Skip ALL methods from anonymous types (structs and enums)
2310                    // These are registered during comptime evaluation with proper Self type context
2311                    if anon_type_method_refs.contains(&inst_ref) {
2312                        continue;
2313                    }
2314                    self.collect_function_signature(
2315                        *name,
2316                        (*params_start, *params_len),
2317                        *return_type,
2318                        *body,
2319                        inst.span,
2320                        FnFlags {
2321                            is_pub: *is_pub,
2322                            is_unchecked: *is_unchecked,
2323                            directives_start: *directives_start,
2324                            directives_len: *directives_len,
2325                        },
2326                    )?;
2327                }
2328
2329                InstData::ConstDecl {
2330                    is_pub, name, init, ..
2331                } => {
2332                    self.collect_const_declaration(*name, *is_pub, *init, inst.span)?;
2333                }
2334
2335                _ => {}
2336            }
2337        }
2338
2339        // ADR-0085: register every fn declared inside a `link_extern("…") { … }`
2340        // block. Gated behind the `c_ffi` preview feature.
2341        self.collect_extern_fn_signatures()?;
2342
2343        Ok(())
2344    }
2345
2346    /// ADR-0085: validate that a `Type` is allowed at the FFI boundary.
2347    ///
2348    /// Allowed: numeric primitives, `bool`, `()`, `Ptr(T)`, `MutPtr(T)`,
2349    /// and `@mark(c) struct T`. Enums are rejected entirely in v1 — see
2350    /// the deferred enum-FFI follow-up. `&T` / `&mut T`, slices, owned
2351    /// containers, and non-C-layout aggregates are rejected here.
2352    fn validate_ffi_type(&self, ty: Type, position: &str, span: Span) -> CompileResult<()> {
2353        // Scalar primitives and unit pass.
2354        if matches!(
2355            ty,
2356            Type::I8
2357                | Type::I16
2358                | Type::I32
2359                | Type::I64
2360                | Type::ISIZE
2361                | Type::U8
2362                | Type::U16
2363                | Type::U32
2364                | Type::U64
2365                | Type::USIZE
2366                | Type::F32
2367                | Type::F64
2368                | Type::BOOL
2369                | Type::UNIT
2370                // ADR-0086 C named arithmetic primitive types.
2371                | Type::C_SCHAR
2372                | Type::C_SHORT
2373                | Type::C_INT
2374                | Type::C_LONG
2375                | Type::C_LONGLONG
2376                | Type::C_UCHAR
2377                | Type::C_USHORT
2378                | Type::C_UINT
2379                | Type::C_ULONG
2380                | Type::C_ULONGLONG
2381                | Type::C_FLOAT
2382                | Type::C_DOUBLE
2383        ) {
2384            return Ok(());
2385        }
2386
2387        // Raw pointers (ADR-0061) cross the boundary as `const T*`/`T*`.
2388        // ADR-0086: `Ptr(c_void)` / `MutPtr(c_void)` are also permitted —
2389        // c_void is allowed inside pointer types but rejected as a value.
2390        if ty.is_ptr_const() || ty.is_ptr_mut() {
2391            return Ok(());
2392        }
2393
2394        // `@mark(c) struct T` is allowed by value.
2395        if let Some(struct_id) = ty.as_struct() {
2396            let def = self.type_pool.struct_def(struct_id);
2397            if def.is_c_layout {
2398                return Ok(());
2399            }
2400            return Err(CompileError::new(
2401                ErrorKind::CFfi(format!(
2402                    "type `{}` cannot cross the FFI boundary {}: \
2403                     non-`@mark(c)` struct (or container) types are not \
2404                     C-ABI compatible",
2405                    def.name, position
2406                )),
2407                span,
2408            ));
2409        }
2410
2411        // ADR-0086: `@mark(c) enum E` is allowed by value. Field-less
2412        // enums lower to a bare `c_int`; data-carrying enums use the
2413        // C tagged-union layout `{ tag: c_int; payload: union<…> }`.
2414        // Variant payload fields must themselves be C-FFI types.
2415        // Non-`@mark(c)` enums stay rejected.
2416        if let Some(enum_id) = ty.as_enum() {
2417            let def = self.type_pool.enum_def(enum_id);
2418            if def.is_c_layout {
2419                let enum_name = def.name.clone();
2420                let variants = def.variants.clone();
2421                // Recursively validate each variant payload field — it
2422                // must itself be an FFI-compatible type. Mirrors the
2423                // recursion the @mark(c) struct path uses.
2424                for variant in &variants {
2425                    for &field_ty in &variant.fields {
2426                        if let Err(mut e) = self.validate_ffi_type(field_ty, position, span) {
2427                            // Wrap the inner diagnostic so the user
2428                            // sees which enum variant payload field
2429                            // is at fault.
2430                            if let ErrorKind::CFfi(inner) = &e.kind {
2431                                e.kind = ErrorKind::CFfi(format!(
2432                                    "variant `{}` of `@mark(c) enum {}` payload field has \
2433                                     a non-FFI type: {}",
2434                                    variant.name, enum_name, inner
2435                                ));
2436                            }
2437                            return Err(e);
2438                        }
2439                    }
2440                }
2441                return Ok(());
2442            }
2443            return Err(CompileError::new(
2444                ErrorKind::CFfi(format!(
2445                    "type `{}` cannot cross the FFI boundary {}: \
2446                     non-`@mark(c)` enum types are not C-ABI compatible",
2447                    def.name, position
2448                )),
2449                span,
2450            ));
2451        }
2452
2453        // Everything else is rejected.
2454        Err(CompileError::new(
2455            ErrorKind::CFfi(format!(
2456                "this type cannot cross the FFI boundary {}: \
2457                 only numeric primitives, `bool`, `()`, `Ptr(T)`, `MutPtr(T)`, \
2458                 and `@mark(c) struct` types are permitted",
2459                position
2460            )),
2461            span,
2462        ))
2463    }
2464
2465    /// ADR-0085: register every C extern fn declared in `link_extern` blocks.
2466    ///
2467    /// Each entry on `Rir::extern_fns()` becomes a `FunctionInfo` with
2468    /// `is_extern = true`, `is_c_abi = true`, and `link_library = Some(...)`.
2469    /// The body slot is filled with a sentinel `InstRef`; codegen sees the
2470    /// `is_extern` flag and emits a declaration only.
2471    fn collect_extern_fn_signatures(&mut self) -> CompileResult<()> {
2472        let extern_fns: Vec<_> = self.rir.extern_fns().to_vec();
2473        let empty_blocks: Vec<_> = self.rir.empty_link_extern_blocks().to_vec();
2474        if extern_fns.is_empty() && empty_blocks.is_empty() {
2475            return Ok(());
2476        }
2477        // ADR-0085: any `link_extern` block fires the c_ffi preview gate.
2478        // Pick the first block-span we see for the diagnostic anchor.
2479        // ADR-0085: validate library names — non-empty per spec 10.2:5.
2480        // (C FFI is stable as of ADR-0085 Phase 5; no preview gate.)
2481        for ext in &extern_fns {
2482            if self.interner.resolve(&ext.library).is_empty() {
2483                return Err(CompileError::new(
2484                    ErrorKind::CFfi(
2485                        "`link_extern(\"…\")` library name must not be empty".to_string(),
2486                    ),
2487                    ext.block_span,
2488                ));
2489            }
2490        }
2491        for (lib, _link_mode, block_span) in &empty_blocks {
2492            if self.interner.resolve(lib).is_empty() {
2493                return Err(CompileError::new(
2494                    ErrorKind::CFfi(
2495                        "`link_extern(\"…\")` library name must not be empty".to_string(),
2496                    ),
2497                    *block_span,
2498                ));
2499            }
2500        }
2501
2502        // ADR-0086: a given library cannot be declared with both
2503        // dynamic (`link_extern`) and static (`static_link_extern`)
2504        // linkage across the same compilation unit. Fire the
2505        // c_ffi_extras preview gate on any `static_link_extern` block
2506        // (the dynamic path is stable from ADR-0085).
2507        use gruel_rir::inst::RirLinkMode;
2508        use rustc_hash::FxHashMap;
2509        let mut linkage_by_lib: FxHashMap<Spur, (RirLinkMode, Span)> = FxHashMap::default();
2510        for ext in &extern_fns {
2511            let entry = linkage_by_lib
2512                .entry(ext.library)
2513                .or_insert((ext.link_mode, ext.block_span));
2514            if entry.0 != ext.link_mode {
2515                let lib_name = self.interner.resolve(&ext.library).to_string();
2516                return Err(CompileError::new(
2517                    ErrorKind::CFfi(format!(
2518                        "library `{}` is declared with both `link_extern` (dynamic) and \
2519                         `static_link_extern` (static) linkage; a library must use a \
2520                         single linkage mode across the compilation unit",
2521                        lib_name
2522                    )),
2523                    ext.block_span,
2524                ));
2525            }
2526        }
2527        for (lib, link_mode, block_span) in &empty_blocks {
2528            let entry = linkage_by_lib
2529                .entry(*lib)
2530                .or_insert((*link_mode, *block_span));
2531            if entry.0 != *link_mode {
2532                let lib_name = self.interner.resolve(lib).to_string();
2533                return Err(CompileError::new(
2534                    ErrorKind::CFfi(format!(
2535                        "library `{}` is declared with both `link_extern` (dynamic) and \
2536                         `static_link_extern` (static) linkage; a library must use a \
2537                         single linkage mode across the compilation unit",
2538                        lib_name
2539                    )),
2540                    *block_span,
2541                ));
2542            }
2543        }
2544
2545        for ext in &extern_fns {
2546            let params = self.rir.get_params(ext.params_start, ext.params_len);
2547            let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
2548            let mut param_types = Vec::with_capacity(params.len());
2549            let mut param_modes = Vec::with_capacity(params.len());
2550            let mut param_comptime = Vec::with_capacity(params.len());
2551            for p in &params {
2552                if p.is_comptime {
2553                    return Err(CompileError::new(
2554                        ErrorKind::CFfi(
2555                            "extern fns inside `link_extern` must not declare \
2556                             comptime parameters"
2557                                .to_string(),
2558                        ),
2559                        ext.span,
2560                    ));
2561                }
2562                let (ty, mode) = self.resolve_param_type(p.ty, p.mode, ext.span)?;
2563                self.validate_ffi_type(ty, "as a parameter type", ext.span)?;
2564                param_types.push(ty);
2565                param_modes.push(mode);
2566                param_comptime.push(false);
2567            }
2568            let ret_type = self.resolve_type(ext.return_type, ext.span)?;
2569            self.validate_ffi_type(ret_type, "as a return type", ext.span)?;
2570            let params_range =
2571                self.param_arena
2572                    .alloc(param_names, param_types, param_modes, param_comptime);
2573
2574            // ADR-0085: process directives on the extern fn. Today the only
2575            // supported one is `@link_name("…")`.
2576            // ADR-0088: extern fns also accept `@mark(unchecked)` and,
2577            // under the `unchecked_fn_extensions` preview gate, are
2578            // required to carry it. The gate fires below after the
2579            // directive scan completes.
2580            let directives = self
2581                .rir
2582                .get_directives(ext.directives_start, ext.directives_len);
2583            let mut link_name_override: Option<Spur> = None;
2584            let mut has_unchecked_mark = false;
2585            for d in directives.iter() {
2586                let dir_name = self.interner.resolve(&d.name).to_string();
2587                match dir_name.as_str() {
2588                    "link_name" => {
2589                        if d.args.len() != 1 {
2590                            return Err(CompileError::new(
2591                                ErrorKind::CFfi(
2592                                    "`@link_name(\"…\")` takes exactly one string argument"
2593                                        .to_string(),
2594                                ),
2595                                d.span,
2596                            ));
2597                        }
2598                        link_name_override = Some(d.args[0]);
2599                    }
2600                    "mark" => {
2601                        // ADR-0085: redundant `@mark(c)` inside a
2602                        // `link_extern` block is permitted. ADR-0088:
2603                        // `@mark(unchecked)` is the required form for
2604                        // every extern fn (gated by preview). Other
2605                        // marker names are rejected.
2606                        for arg in &d.args {
2607                            let name = self.interner.resolve(arg);
2608                            if name == "c" {
2609                                // accepted, no-op
2610                            } else if name == "unchecked" {
2611                                has_unchecked_mark = true;
2612                            } else {
2613                                return Err(CompileError::new(
2614                                    ErrorKind::CFfi(format!(
2615                                        "marker `{}` is not valid on extern fns inside `link_extern`",
2616                                        name
2617                                    )),
2618                                    d.span,
2619                                ));
2620                            }
2621                        }
2622                    }
2623                    other => {
2624                        return Err(CompileError::new(
2625                            ErrorKind::CFfi(format!(
2626                                "directive `@{}` is not valid on extern fns inside `link_extern`",
2627                                other
2628                            )),
2629                            d.span,
2630                        ));
2631                    }
2632                }
2633            }
2634
2635            // ADR-0088: every extern fn must carry `@mark(unchecked)`.
2636            // FFI imports are unverified from the Gruel side by
2637            // construction; the marker makes the call-site discipline
2638            // visible — every caller must wrap the call in a
2639            // `checked { }` block.
2640            if !has_unchecked_mark {
2641                let fn_name = self.interner.resolve(&ext.name).to_string();
2642                let library = self.interner.resolve(&ext.library).to_string();
2643                return Err(CompileError::new(
2644                    ErrorKind::ExternFnMissingUnchecked { fn_name, library },
2645                    ext.span,
2646                ));
2647            }
2648
2649            if self.functions.contains_key(&ext.name) {
2650                let name_str = self.interner.resolve(&ext.name).to_string();
2651                return Err(CompileError::new(
2652                    ErrorKind::DuplicateTypeDefinition {
2653                        type_name: format!("function `{}`", name_str),
2654                    },
2655                    ext.span,
2656                ));
2657            }
2658
2659            self.functions.insert(
2660                ext.name,
2661                FunctionInfo {
2662                    params: params_range,
2663                    return_type: ret_type,
2664                    return_type_sym: ext.return_type,
2665                    body: InstRef::from_raw(u32::MAX),
2666                    span: ext.span,
2667                    is_generic: false,
2668                    is_pub: true,
2669                    // ADR-0088: every extern fn that carries
2670                    // `@mark(unchecked)` is treated as unchecked at
2671                    // call sites — every call must sit inside a
2672                    // `checked { }` block.
2673                    is_unchecked: has_unchecked_mark,
2674                    file_id: ext.span.file_id,
2675                    canonical_name: None,
2676                    link_library: Some(ext.library),
2677                    link_name: link_name_override,
2678                    is_extern: true,
2679                    is_c_abi: true,
2680                },
2681            );
2682        }
2683        Ok(())
2684    }
2685
2686    /// Collect a function signature for forward reference.
2687    fn collect_function_signature(
2688        &mut self,
2689        name: Spur,
2690        (params_start, params_len): (u32, u32),
2691        return_type_sym: Spur,
2692        body: InstRef,
2693        span: Span,
2694        flags: FnFlags,
2695    ) -> CompileResult<()> {
2696        let FnFlags {
2697            is_pub,
2698            is_unchecked,
2699            directives_start,
2700            directives_len,
2701        } = flags;
2702
2703        // ADR-0085: scan directives for `@mark(c)` (C ABI export) and
2704        // `@link_name("…")` (symbol override). Other markers and
2705        // directives are handled by their own passes; this is just the
2706        // C-FFI subset.
2707        //
2708        // ADR-0088: `@mark(unchecked)` on a fn / method is gated by the
2709        // `UncheckedFnExtensions` preview feature; the gate fires
2710        // centrally in `validate_directives`. The legacy `unchecked`
2711        // keyword path is unaffected.
2712        let mut is_c_abi = false;
2713        let mut link_name_override: Option<Spur> = None;
2714        let directives = self.rir.get_directives(directives_start, directives_len);
2715        for d in directives.iter() {
2716            let dir_name = self.interner.resolve(&d.name).to_string();
2717            match dir_name.as_str() {
2718                "mark" => {
2719                    for arg in &d.args {
2720                        let marker_name = self.interner.resolve(arg);
2721                        if marker_name == "c" {
2722                            is_c_abi = true;
2723                        }
2724                    }
2725                }
2726                "link_name" => {
2727                    if d.args.len() != 1 {
2728                        return Err(CompileError::new(
2729                            ErrorKind::CFfi(
2730                                "`@link_name(\"…\")` takes exactly one string argument".to_string(),
2731                            ),
2732                            d.span,
2733                        ));
2734                    }
2735                    link_name_override = Some(d.args[0]);
2736                }
2737                _ => {}
2738            }
2739        }
2740
2741        // ADR-0085: `@link_name` is only valid on C-ABI fns (extern
2742        // declarations or `@mark(c)` exports).
2743        if link_name_override.is_some() && !is_c_abi {
2744            return Err(CompileError::new(
2745                ErrorKind::CFfi(
2746                    "`@link_name(\"…\")` is only valid on `@mark(c)` functions \
2747                     or extern fns inside `link_extern`"
2748                        .to_string(),
2749                ),
2750                span,
2751            ));
2752        }
2753        let params = self.rir.get_params(params_start, params_len);
2754
2755        let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
2756
2757        // Check if this function has any comptime TYPE parameters (not value parameters).
2758        // A type parameter is a comptime param whose declared type is either:
2759        // - `type` (unbounded): `comptime T: type`
2760        // - a registered interface name (ADR-0056): `comptime T: SomeInterface`
2761        //
2762        // Both cases erase the parameter at codegen and substitute the
2763        // concrete type at specialization. Interface-bounded parameters
2764        // additionally trigger a conformance check at the call site.
2765        let type_sym = self.interner.get_or_intern("type");
2766        // Pre-compute per-param flags so the closure doesn't need to borrow
2767        // `self` (which conflicts with the resolve_type call below).
2768        let is_type_param: Vec<bool> = params
2769            .iter()
2770            .map(|p| p.is_comptime && (p.ty == type_sym || self.interfaces.contains_key(&p.ty)))
2771            .collect();
2772        // Any comptime parameter — type or value — makes the function generic.
2773        // Comptime value parameters (e.g. `comptime n: i32`) need per-call
2774        // specialization too: the body may contain a `comptime if n < 0 {…}`
2775        // guard that can only be evaluated once `n` is bound to a concrete
2776        // value at the call site.
2777        let is_generic = params.iter().any(|p| p.is_comptime);
2778
2779        // Collect type parameter names.
2780        let type_param_names: Vec<Spur> = params
2781            .iter()
2782            .zip(is_type_param.iter())
2783            .filter_map(|(p, &b)| if b { Some(p.name) } else { None })
2784            .collect();
2785
2786        // Record any interface bounds for use at specialization time.
2787        for p in params.iter() {
2788            if p.is_comptime
2789                && let Some(iid) = self.interfaces.get(&p.ty).copied()
2790            {
2791                self.comptime_interface_bounds.insert((name, p.name), iid);
2792            }
2793        }
2794
2795        // For generic functions, we defer type resolution of type parameters until specialization.
2796        // We use Type::COMPTIME_TYPE as a placeholder for comptime type parameters.
2797        // ADR-0076 Phase 2: also collect the (possibly normalized) param mode
2798        // returned by `resolve_param_type` so `Ref(I)` / `MutRef(I)` lower to
2799        // the same `(Interface, Borrow|Inout)` shape as legacy `borrow t: I`
2800        // / `inout t: I`.
2801        //
2802        // A parameter type "references" a type parameter when bare equality
2803        // misses but a substituted resolution succeeds — e.g. `MutRef(Vec(T))`
2804        // mentions `T` indirectly. Treat those exactly like the bare-name
2805        // case: resolve at specialization, placeholder here. Mirrors the
2806        // `references_method_type_param` helper used for inline methods.
2807        let references_type_param = |ty_sym: Spur, sema: &mut Self| -> bool {
2808            if type_param_names.contains(&ty_sym) {
2809                return true;
2810            }
2811            if type_param_names.is_empty() {
2812                return false;
2813            }
2814            let subst: rustc_hash::FxHashMap<Spur, Type> =
2815                type_param_names.iter().map(|&n| (n, Type::I32)).collect();
2816            let with_subst = sema.resolve_type_for_comptime_with_subst(ty_sym, &subst);
2817            let without_subst =
2818                sema.resolve_type_for_comptime_with_subst(ty_sym, &HashMap::default());
2819            with_subst.is_some() && without_subst.is_none()
2820        };
2821
2822        let mut param_types: Vec<Type> = Vec::with_capacity(params.len());
2823        let mut param_modes: Vec<RirParamMode> = Vec::with_capacity(params.len());
2824        for (p, &is_tp) in params.iter().zip(is_type_param.iter()) {
2825            let (ty, mode) = if is_tp || references_type_param(p.ty, self) {
2826                // Comptime type parameter, or a parameter whose declared type
2827                // is (or transitively contains) a type parameter — e.g.
2828                // `x: T`, `v: MutRef(Vec(T))`. The concrete type is resolved
2829                // at specialization; mode is taken verbatim from RIR.
2830                //
2831                // ADR-0076 normalization for the deferred case: when the
2832                // declared type is `Ref(...)` / `MutRef(...)` wrapping a
2833                // type parameter, surface that ref-shape on the param mode.
2834                // `resolve_param_type` does this for the eager path (and
2835                // for interface inner types); here we do the same shape
2836                // detection on the source-level symbol so the implicit-
2837                // forwarding logic in `analyze_call` (which can only see
2838                // the deferred `COMPTIME_TYPE` placeholder, not the
2839                // original `Ref(...)` shape) can still recognize the
2840                // reference parameter and promote the arg mode.
2841                let normalized_mode = {
2842                    let type_name = self.interner.resolve(&p.ty);
2843                    if let Some((callee, args)) = parse_type_call_syntax(type_name)
2844                        && args.len() == 1
2845                        && (callee == "Ref" || callee == "MutRef")
2846                        && matches!(p.mode, RirParamMode::Normal)
2847                    {
2848                        if callee == "MutRef" {
2849                            RirParamMode::MutRef
2850                        } else {
2851                            RirParamMode::Ref
2852                        }
2853                    } else {
2854                        p.mode
2855                    }
2856                };
2857                (Type::COMPTIME_TYPE, normalized_mode)
2858            } else {
2859                self.resolve_param_type(p.ty, p.mode, span)?
2860            };
2861            param_types.push(ty);
2862            param_modes.push(mode);
2863        }
2864        let param_comptime: Vec<bool> = params.iter().map(|p| p.is_comptime).collect();
2865
2866        // Return type follows the same rule.
2867        let ret_type = if references_type_param(return_type_sym, self) {
2868            Type::COMPTIME_TYPE
2869        } else {
2870            self.resolve_type(return_type_sym, span)?
2871        };
2872
2873        // Allocate parameter data in the arena
2874        let params_range =
2875            self.param_arena
2876                .alloc(param_names, param_types, param_modes, param_comptime);
2877
2878        // ADR-0085: validate FFI types for `@mark(c)` exports — params
2879        // and return type must all be C-ABI compatible. Skipped for
2880        // regular Gruel fns.
2881        if is_c_abi {
2882            for ty in self.param_arena.types(params_range) {
2883                self.validate_ffi_type(*ty, "as a parameter type", span)?;
2884            }
2885            self.validate_ffi_type(ret_type, "as a return type", span)?;
2886        }
2887
2888        self.functions.insert(
2889            name,
2890            FunctionInfo {
2891                params: params_range,
2892                return_type: ret_type,
2893                return_type_sym,
2894                body,
2895                span,
2896                is_generic,
2897                is_pub,
2898                is_unchecked,
2899                file_id: span.file_id,
2900                canonical_name: None,
2901                link_library: None,
2902                link_name: link_name_override,
2903                is_extern: false,
2904                is_c_abi,
2905            },
2906        );
2907        Ok(())
2908    }
2909
2910    /// Collect methods defined inline in a struct.
2911    fn collect_struct_methods(
2912        &mut self,
2913        type_name: Spur,
2914        methods_start: u32,
2915        methods_len: u32,
2916        span: Span,
2917    ) -> CompileResult<()> {
2918        let struct_id = match self.structs.get(&type_name) {
2919            Some(id) => *id,
2920            None => {
2921                let type_name_str = self.interner.resolve(&type_name).to_string();
2922                return Err(CompileError::new(
2923                    ErrorKind::UnknownType(type_name_str),
2924                    span,
2925                ));
2926            }
2927        };
2928        let struct_type = Type::new_struct(struct_id);
2929        let drop_name_sym = self.interner.get_or_intern("__drop");
2930
2931        // ADR-0076: bind `Self` to the host struct while resolving method
2932        // signatures. Errors abort the whole analysis pass so a leak is
2933        // harmless; on the success path we restore the previous binding.
2934        let saved_self = self.current_self.replace(struct_type);
2935
2936        let methods = self.rir.get_inst_refs(methods_start, methods_len);
2937        for method_ref in methods {
2938            let method_inst = self.rir.get(method_ref);
2939            if let InstData::FnDecl {
2940                name: method_name,
2941                is_pub: method_is_pub,
2942                is_unchecked,
2943                params_start,
2944                params_len,
2945                return_type,
2946                body,
2947                has_self,
2948                receiver_mode,
2949                directives_start,
2950                directives_len,
2951                ..
2952            } = &method_inst.data
2953            {
2954                let receiver = decode_receiver_mode(*receiver_mode);
2955
2956                // ADR-0088: reject `@mark(unchecked) fn __drop`. Drop
2957                // glue runs implicitly at scope exit; there is no
2958                // caller `checked { }` to gate it. The preview gate
2959                // itself fires centrally in `validate_directives`.
2960                if *method_name == drop_name_sym
2961                    && self.directives_have_mark_unchecked(*directives_start, *directives_len)
2962                {
2963                    return Err(CompileError::new(
2964                        ErrorKind::UncheckedDestructor,
2965                        method_inst.span,
2966                    ));
2967                }
2968
2969                // ADR-0053: a method named `__drop` is the struct's destructor,
2970                // not a regular method. Route it through the destructor slot.
2971                if *method_name == drop_name_sym {
2972                    self.register_inline_struct_drop(
2973                        type_name,
2974                        struct_id,
2975                        DropSignature {
2976                            has_self: *has_self,
2977                            params_len: *params_len,
2978                            return_type: *return_type,
2979                            body: *body,
2980                            span: method_inst.span,
2981                        },
2982                    )?;
2983                    continue;
2984                }
2985
2986                // Use StructId in key to support anonymous struct methods
2987                let key = (struct_id, *method_name);
2988                if self.methods.contains_key(&key) {
2989                    let type_name_str = self.interner.resolve(&type_name).to_string();
2990                    let method_name_str = self.interner.resolve(method_name).to_string();
2991                    return Err(CompileError::new(
2992                        ErrorKind::DuplicateMethod {
2993                            type_name: type_name_str,
2994                            method_name: method_name_str,
2995                        },
2996                        method_inst.span,
2997                    ));
2998                }
2999
3000                let params = self.rir.get_params(*params_start, *params_len);
3001                let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
3002                let param_comptime: Vec<bool> = params.iter().map(|p| p.is_comptime).collect();
3003
3004                // Detect method-level comptime type parameters (e.g.,
3005                // `fn apply(self, comptime F: type, f: F) -> T`). When
3006                // present, the method is generic at the method level — its
3007                // param types that reference those names (`f: F`) and its
3008                // return type cannot be fully resolved until the method is
3009                // specialized at a call site. Mirrors the top-level
3010                // generic-function treatment above.
3011                //
3012                // ADR-0056: an interface-bounded comptime parameter
3013                // (`comptime T: SomeInterface`) is also a type parameter; we
3014                // record the bound for the conformance check at the call
3015                // site. The method-key is encoded as "StructName.method" to
3016                // share the bound side-table with top-level functions.
3017                let type_sym = self.interner.get_or_intern("type");
3018                let method_type_param_names: Vec<Spur> = params
3019                    .iter()
3020                    .filter(|p| {
3021                        p.is_comptime && (p.ty == type_sym || self.interfaces.contains_key(&p.ty))
3022                    })
3023                    .map(|p| p.name)
3024                    .collect();
3025                let is_method_generic = !method_type_param_names.is_empty();
3026
3027                // Record interface bounds for this method.
3028                if !method_type_param_names.is_empty() {
3029                    let owner_str = format!(
3030                        "{}.{}",
3031                        self.interner.resolve(&type_name),
3032                        self.interner.resolve(method_name)
3033                    );
3034                    let owner = self.interner.get_or_intern(&owner_str);
3035                    for p in params.iter() {
3036                        if p.is_comptime
3037                            && let Some(iid) = self.interfaces.get(&p.ty).copied()
3038                        {
3039                            self.comptime_interface_bounds.insert((owner, p.name), iid);
3040                        }
3041                    }
3042                }
3043
3044                // Helper: does a type symbol mention any of our method-level
3045                // type param names? Covers bare `T`, compound `[T; N]`,
3046                // `ptr const T`, etc. — check via the comptime-substitution
3047                // resolver using a sentinel substitution.
3048                let references_method_type_param = |ty_sym: Spur, sema: &mut Self| -> bool {
3049                    if method_type_param_names.contains(&ty_sym) {
3050                        return true;
3051                    }
3052                    let subst: rustc_hash::FxHashMap<Spur, Type> = method_type_param_names
3053                        .iter()
3054                        .map(|&n| (n, Type::I32))
3055                        .collect();
3056                    let with_subst = sema.resolve_type_for_comptime_with_subst(ty_sym, &subst);
3057                    let without_subst =
3058                        sema.resolve_type_for_comptime_with_subst(ty_sym, &HashMap::default());
3059                    with_subst.is_some() && without_subst.is_none()
3060                };
3061
3062                // ADR-0076 Phase 2: collect normalized modes alongside types
3063                // so `Ref(I)` / `MutRef(I)` lower like the legacy keyword form.
3064                let mut param_types: Vec<Type> = Vec::with_capacity(params.len());
3065                let mut param_modes: Vec<RirParamMode> = Vec::with_capacity(params.len());
3066                for p in &params {
3067                    let (ty, mode) = if (p.is_comptime && p.ty == type_sym)
3068                        || references_method_type_param(p.ty, self)
3069                    {
3070                        (Type::COMPTIME_TYPE, p.mode)
3071                    } else {
3072                        self.resolve_param_type(p.ty, p.mode, method_inst.span)?
3073                    };
3074                    param_types.push(ty);
3075                    param_modes.push(mode);
3076                }
3077                let ret_type = if references_method_type_param(*return_type, self) {
3078                    Type::COMPTIME_TYPE
3079                } else {
3080                    self.resolve_type(*return_type, method_inst.span)?
3081                };
3082
3083                // Allocate method parameters in the arena, preserving mode
3084                // and comptime flags so specialization can pick them up.
3085                let param_range =
3086                    self.param_arena
3087                        .alloc(param_names, param_types, param_modes, param_comptime);
3088
3089                self.methods.insert(
3090                    key,
3091                    MethodInfo {
3092                        struct_type,
3093                        has_self: *has_self,
3094                        receiver,
3095                        params: param_range,
3096                        return_type: ret_type,
3097                        body: *body,
3098                        span: method_inst.span,
3099                        is_unchecked: *is_unchecked,
3100                        is_generic: is_method_generic,
3101                        return_type_sym: *return_type,
3102                        is_pub: *method_is_pub,
3103                        file_id: method_inst.span.file_id,
3104                    },
3105                );
3106            }
3107        }
3108        self.current_self = saved_self;
3109        Ok(())
3110    }
3111
3112    /// Register an inline `fn __drop(self)` as a struct's destructor.
3113    ///
3114    /// Validates the signature (must be exactly `fn __drop(self)`, no extra
3115    /// params, returns unit) and the type's copy/linear status (a destructor
3116    /// is illegal on `@derive(Copy)` and `linear` types per ADR-0053).
3117    fn register_inline_struct_drop(
3118        &mut self,
3119        type_name: Spur,
3120        struct_id: StructId,
3121        sig: DropSignature,
3122    ) -> CompileResult<()> {
3123        let DropSignature {
3124            has_self,
3125            params_len,
3126            return_type,
3127            body,
3128            span,
3129        } = sig;
3130        let type_name_str = self.interner.resolve(&type_name).to_string();
3131
3132        // Signature check: `fn __drop(self)`, no extra params, no non-unit return.
3133        if !has_self {
3134            return Err(CompileError::new(
3135                ErrorKind::InvalidInlineDrop {
3136                    type_name: type_name_str.clone(),
3137                    reason: "must take `self` — found an associated function".into(),
3138                },
3139                span,
3140            ));
3141        }
3142        if params_len > 0 {
3143            return Err(CompileError::new(
3144                ErrorKind::InvalidInlineDrop {
3145                    type_name: type_name_str.clone(),
3146                    reason: "must take only `self` — extra parameters are not allowed".into(),
3147                },
3148                span,
3149            ));
3150        }
3151        let ret_str = self.interner.resolve(&return_type);
3152        if ret_str != "()" {
3153            return Err(CompileError::new(
3154                ErrorKind::InvalidInlineDrop {
3155                    type_name: type_name_str.clone(),
3156                    reason: format!("must return unit — found return type `{}`", ret_str),
3157                },
3158                span,
3159            ));
3160        }
3161
3162        // Affine-only: `@derive(Copy)` structs cannot have destructors (double-free risk).
3163        // `linear` structs cannot either — they are never implicitly dropped, so
3164        // a destructor would be unreachable.
3165        let struct_def_snapshot = self.type_pool.struct_def(struct_id);
3166        match struct_def_snapshot.posture {
3167            Posture::Copy => {
3168                return Err(CompileError::new(
3169                    ErrorKind::InvalidInlineDrop {
3170                        type_name: type_name_str.clone(),
3171                        reason:
3172                            "`@derive(Copy)` types cannot declare `fn __drop` (would double-free on copy)"
3173                                .into(),
3174                    },
3175                    span,
3176                ));
3177            }
3178            Posture::Linear => {
3179                return Err(CompileError::new(
3180                    ErrorKind::InvalidInlineDrop {
3181                        type_name: type_name_str.clone(),
3182                        reason: "`linear` types cannot declare `fn __drop` (linear values are never implicitly dropped)".into(),
3183                    },
3184                    span,
3185                ));
3186            }
3187            Posture::Affine => {}
3188        }
3189
3190        // ADR-0085: `fn __drop` on `@mark(c)` structs is forbidden in
3191        // v1. The C side owns cross-boundary cleanup discipline; mixing
3192        // Gruel destructor semantics with raw extern calls is left to a
3193        // future ADR with defined cross-boundary semantics.
3194        if struct_def_snapshot.is_c_layout {
3195            return Err(CompileError::new(
3196                ErrorKind::CFfi(format!(
3197                    "`@mark(c) struct {}` cannot declare `fn __drop`; manual cleanup is required \
3198                     through raw pointer discipline",
3199                    type_name_str
3200                )),
3201                span,
3202            ));
3203        }
3204
3205        // Only one destructor per type.
3206        let mut struct_def = struct_def_snapshot;
3207        if struct_def.destructor.is_some() {
3208            return Err(CompileError::new(
3209                ErrorKind::DuplicateDestructor {
3210                    type_name: type_name_str.clone(),
3211                },
3212                span,
3213            ));
3214        }
3215        let destructor_name = format!("{}.__drop", type_name_str);
3216        struct_def.destructor = Some(destructor_name);
3217        self.type_pool.update_struct_def(struct_id, struct_def);
3218
3219        self.inline_struct_drops.insert(struct_id, (body, span));
3220        Ok(())
3221    }
3222
3223    /// Register an inline `fn __drop(self)` as an enum's destructor (ADR-0053 phase 3b).
3224    ///
3225    /// Mirrors `register_inline_struct_drop`. Validates the signature
3226    /// (must be `fn __drop(self)` returning unit, only one per type) and
3227    /// stores the destructor metadata in `EnumDef.destructor`.
3228    fn register_inline_enum_drop(
3229        &mut self,
3230        type_name: Spur,
3231        enum_id: EnumId,
3232        sig: DropSignature,
3233    ) -> CompileResult<()> {
3234        let DropSignature {
3235            has_self,
3236            params_len,
3237            return_type,
3238            body,
3239            span,
3240        } = sig;
3241        let type_name_str = self.interner.resolve(&type_name).to_string();
3242
3243        if !has_self {
3244            return Err(CompileError::new(
3245                ErrorKind::InvalidInlineDrop {
3246                    type_name: type_name_str,
3247                    reason: "must take `self` — found an associated function".into(),
3248                },
3249                span,
3250            ));
3251        }
3252        if params_len > 0 {
3253            return Err(CompileError::new(
3254                ErrorKind::InvalidInlineDrop {
3255                    type_name: type_name_str,
3256                    reason: "must take only `self` — extra parameters are not allowed".into(),
3257                },
3258                span,
3259            ));
3260        }
3261        let ret_str = self.interner.resolve(&return_type);
3262        if ret_str != "()" {
3263            return Err(CompileError::new(
3264                ErrorKind::InvalidInlineDrop {
3265                    type_name: type_name_str,
3266                    reason: format!("must return unit — found return type `{}`", ret_str),
3267                },
3268                span,
3269            ));
3270        }
3271
3272        let mut enum_def = self.type_pool.enum_def(enum_id);
3273        if enum_def.destructor.is_some() {
3274            return Err(CompileError::new(
3275                ErrorKind::DuplicateDestructor {
3276                    type_name: type_name_str,
3277                },
3278                span,
3279            ));
3280        }
3281        let destructor_name = format!("{}.__drop", type_name_str);
3282        enum_def.destructor = Some(destructor_name);
3283        self.type_pool.update_enum_def(enum_id, enum_def);
3284
3285        self.inline_enum_drops.insert(enum_id, (body, span));
3286        Ok(())
3287    }
3288
3289    /// Collect methods defined inline in a named enum (ADR-0053).
3290    ///
3291    /// Mirrors `collect_struct_methods`: registers each method against the
3292    /// enum's `EnumId` in `self.enum_methods`, which the method-resolution
3293    /// machinery already consults for anonymous enums (ADR-0039).
3294    fn collect_enum_methods(
3295        &mut self,
3296        type_name: Spur,
3297        methods_start: u32,
3298        methods_len: u32,
3299        span: Span,
3300    ) -> CompileResult<()> {
3301        if methods_len == 0 {
3302            return Ok(());
3303        }
3304        let enum_id = match self.enums.get(&type_name) {
3305            Some(id) => *id,
3306            None => {
3307                let type_name_str = self.interner.resolve(&type_name).to_string();
3308                return Err(CompileError::new(
3309                    ErrorKind::UnknownType(type_name_str),
3310                    span,
3311                ));
3312            }
3313        };
3314        let enum_type = Type::new_enum(enum_id);
3315        let drop_name_sym = self.interner.get_or_intern("__drop");
3316
3317        // ADR-0076: bind `Self` to the host enum while resolving method
3318        // signatures.
3319        let saved_self = self.current_self.replace(enum_type);
3320
3321        let methods = self.rir.get_inst_refs(methods_start, methods_len);
3322        for method_ref in methods {
3323            let method_inst = self.rir.get(method_ref);
3324            if let InstData::FnDecl {
3325                name: method_name,
3326                is_pub: method_is_pub,
3327                is_unchecked,
3328                params_start,
3329                params_len,
3330                return_type,
3331                body,
3332                has_self,
3333                receiver_mode,
3334                directives_start,
3335                directives_len,
3336                ..
3337            } = &method_inst.data
3338            {
3339                let receiver = decode_receiver_mode(*receiver_mode);
3340
3341                // ADR-0088: reject `@mark(unchecked) fn __drop`.
3342                if *method_name == drop_name_sym
3343                    && self.directives_have_mark_unchecked(*directives_start, *directives_len)
3344                {
3345                    return Err(CompileError::new(
3346                        ErrorKind::UncheckedDestructor,
3347                        method_inst.span,
3348                    ));
3349                }
3350
3351                // ADR-0053 phase 3b: a method named `__drop` is the enum's destructor,
3352                // not a regular method. Route it through the per-enum destructor slot.
3353                if *method_name == drop_name_sym {
3354                    self.register_inline_enum_drop(
3355                        type_name,
3356                        enum_id,
3357                        DropSignature {
3358                            has_self: *has_self,
3359                            params_len: *params_len,
3360                            return_type: *return_type,
3361                            body: *body,
3362                            span: method_inst.span,
3363                        },
3364                    )?;
3365                    continue;
3366                }
3367                let key = (enum_id, *method_name);
3368                if self.enum_methods.contains_key(&key) {
3369                    let type_name_str = self.interner.resolve(&type_name).to_string();
3370                    let method_name_str = self.interner.resolve(method_name).to_string();
3371                    return Err(CompileError::new(
3372                        ErrorKind::DuplicateMethod {
3373                            type_name: type_name_str,
3374                            method_name: method_name_str,
3375                        },
3376                        method_inst.span,
3377                    ));
3378                }
3379
3380                let params = self.rir.get_params(*params_start, *params_len);
3381                let param_names: Vec<Spur> = params.iter().map(|p| p.name).collect();
3382                let param_types: Vec<Type> = params
3383                    .iter()
3384                    .map(|p| self.resolve_type(p.ty, method_inst.span))
3385                    .collect::<CompileResult<Vec<_>>>()?;
3386                let ret_type = self.resolve_type(*return_type, method_inst.span)?;
3387
3388                let param_range = self
3389                    .param_arena
3390                    .alloc_method(param_names.into_iter(), param_types.into_iter());
3391
3392                self.enum_methods.insert(
3393                    key,
3394                    MethodInfo {
3395                        struct_type: enum_type,
3396                        has_self: *has_self,
3397                        receiver,
3398                        params: param_range,
3399                        return_type: ret_type,
3400                        body: *body,
3401                        span: method_inst.span,
3402                        is_unchecked: *is_unchecked,
3403                        // Enum methods do not yet support method-level
3404                        // comptime type params (ADR-0055 defers that path).
3405                        is_generic: false,
3406                        return_type_sym: *return_type,
3407                        is_pub: *method_is_pub,
3408                        file_id: method_inst.span.file_id,
3409                    },
3410                );
3411            }
3412        }
3413        self.current_self = saved_self;
3414        Ok(())
3415    }
3416
3417    /// Collect a constant declaration.
3418    ///
3419    /// Constants are compile-time values. In the module system, they're primarily
3420    /// used for re-exports:
3421    /// ```gruel
3422    /// pub const strings = @import("utils/strings.gruel");
3423    /// ```
3424    ///
3425    /// When the initializer is an `@import(...)`, we evaluate it at compile time
3426    /// to resolve the module and register it in the module registry. This enables
3427    /// subsequent member access via `const_name.function()` syntax.
3428    fn collect_const_declaration(
3429        &mut self,
3430        name: Spur,
3431        is_pub: bool,
3432        init: InstRef,
3433        span: Span,
3434    ) -> CompileResult<()> {
3435        // ADR-0078: detect item-level re-exports first. `pub const X = mod.Y`
3436        // makes X an alias for `mod.Y`, registering it in the appropriate
3437        // name table (functions/structs/enums/interfaces) so call sites
3438        // can use X transparently. The fallback path below handles
3439        // `pub const X = @import("...")` (whole-module re-export) and
3440        // primitive constants.
3441        if self.try_collect_reexport(name, is_pub, init, span)? {
3442            return Ok(());
3443        }
3444
3445        let name_str = self.interner.resolve(&name).to_string();
3446
3447        // Check for duplicate constant names
3448        if self.constants.contains_key(&name) {
3449            return Err(CompileError::new(
3450                ErrorKind::DuplicateConstant {
3451                    name: name_str,
3452                    kind: "constant".to_string(),
3453                },
3454                span,
3455            ));
3456        }
3457
3458        // Check for collision with function names
3459        if self.functions.contains_key(&name) {
3460            return Err(CompileError::new(
3461                ErrorKind::DuplicateConstant {
3462                    name: name_str.clone(),
3463                    kind: "constant (conflicts with function)".to_string(),
3464                },
3465                span,
3466            ));
3467        }
3468
3469        // Evaluate the initializer at compile time to determine the constant type.
3470        let const_type = self.evaluate_const_init(init, span)?;
3471
3472        self.constants.insert(
3473            name,
3474            ConstInfo {
3475                is_pub,
3476                ty: const_type,
3477                init,
3478                span,
3479            },
3480        );
3481
3482        Ok(())
3483    }
3484
3485    /// ADR-0078: Try to handle an item-level re-export of the form
3486    /// `pub const X = some_module_const.Y`. Returns `Ok(true)` if the
3487    /// constant was recognized as a re-export and registered as an alias;
3488    /// `Ok(false)` if this isn't a re-export and the regular const path
3489    /// should run; `Err(...)` if it looks like a re-export but the item
3490    /// can't be resolved.
3491    fn try_collect_reexport(
3492        &mut self,
3493        name: Spur,
3494        is_pub: bool,
3495        init: InstRef,
3496        span: Span,
3497    ) -> CompileResult<bool> {
3498        let inst = self.rir.get(init);
3499        let InstData::FieldGet { base, field } = &inst.data else {
3500            return Ok(false);
3501        };
3502        let field = *field;
3503
3504        // The base must be a `VarRef` to a const that holds a module.
3505        let base_inst = self.rir.get(*base);
3506        let InstData::VarRef { name: base_name } = &base_inst.data else {
3507            return Ok(false);
3508        };
3509        let module_id = match self.constants.get(base_name).and_then(|c| c.ty.as_module()) {
3510            Some(id) => id,
3511            None => return Ok(false),
3512        };
3513        let module_file_path = self.module_registry.get_def(module_id).file_path.clone();
3514        let same_name = name == field;
3515
3516        // Look the field up across each item kind, restricted to items
3517        // declared in the imported module's file.
3518
3519        // Function.
3520        if let Some(fn_info) = self.functions.get(&field).copied() {
3521            let fn_path = self
3522                .get_file_path(fn_info.file_id)
3523                .map(|s| s.to_string())
3524                .unwrap_or_default();
3525            if fn_path == module_file_path {
3526                if !same_name {
3527                    self.check_alias_collision(name, span)?;
3528                    // Resolve to the canonical (non-aliased) name so chains
3529                    // of re-exports collapse to the original function symbol.
3530                    let canonical = fn_info.canonical_name.unwrap_or(field);
3531                    let alias = FunctionInfo {
3532                        is_pub,
3533                        canonical_name: Some(canonical),
3534                        ..fn_info
3535                    };
3536                    self.functions.insert(name, alias);
3537                }
3538                return Ok(true);
3539            }
3540        }
3541
3542        // Struct.
3543        if let Some(&struct_id) = self.structs.get(&field) {
3544            let s_path = self
3545                .get_file_path(self.type_pool.struct_def(struct_id).file_id)
3546                .map(|s| s.to_string())
3547                .unwrap_or_default();
3548            if s_path == module_file_path {
3549                if !same_name {
3550                    self.check_alias_collision(name, span)?;
3551                    self.structs.insert(name, struct_id);
3552                }
3553                return Ok(true);
3554            }
3555        }
3556
3557        // Enum.
3558        if let Some(&enum_id) = self.enums.get(&field) {
3559            let e_path = self
3560                .get_file_path(self.type_pool.enum_def(enum_id).file_id)
3561                .map(|s| s.to_string())
3562                .unwrap_or_default();
3563            if e_path == module_file_path {
3564                if !same_name {
3565                    self.check_alias_collision(name, span)?;
3566                    self.enums.insert(name, enum_id);
3567                }
3568                return Ok(true);
3569            }
3570        }
3571
3572        // Interface.
3573        if let Some(&iface_id) = self.interfaces.get(&field) {
3574            let iface_def = &self.interface_defs[iface_id.0 as usize];
3575            let i_path = self
3576                .get_file_path(iface_def.file_id)
3577                .map(|s| s.to_string())
3578                .unwrap_or_default();
3579            if i_path == module_file_path {
3580                if !same_name {
3581                    self.check_alias_collision(name, span)?;
3582                    self.interfaces.insert(name, iface_id);
3583                }
3584                return Ok(true);
3585            }
3586        }
3587
3588        // Field exists in the module's namespace but doesn't resolve to a
3589        // re-exportable item. Could be e.g. `mod.NotAnItem` — surface as a
3590        // standard "unknown module member" error.
3591        let module_def = self.module_registry.get_def(module_id);
3592        let module_name = module_def.import_path.clone();
3593        let field_str = self.interner.resolve(&field).to_string();
3594        Err(CompileError::new(
3595            ErrorKind::UnknownModuleMember {
3596                module_name,
3597                member_name: field_str,
3598            },
3599            span,
3600        ))
3601    }
3602
3603    /// Collision check for a re-export alias. The alias name must not
3604    /// shadow an existing function/struct/enum/interface/constant.
3605    fn check_alias_collision(&self, name: Spur, span: Span) -> CompileResult<()> {
3606        let name_str = self.interner.resolve(&name).to_string();
3607        if self.functions.contains_key(&name)
3608            || self.structs.contains_key(&name)
3609            || self.enums.contains_key(&name)
3610            || self.interfaces.contains_key(&name)
3611            || self.constants.contains_key(&name)
3612        {
3613            return Err(CompileError::new(
3614                ErrorKind::DuplicateConstant {
3615                    name: name_str,
3616                    kind: "re-export alias".to_string(),
3617                },
3618                span,
3619            ));
3620        }
3621        Ok(())
3622    }
3623
3624    /// Evaluate a constant initializer at compile time.
3625    ///
3626    /// Currently handles:
3627    /// - `@import("path")` - Returns Type::Module
3628    /// - Integer literals - Returns the integer type
3629    ///
3630    /// Future extensions (ADR-0025 comptime) will support:
3631    /// - Arithmetic on constants
3632    /// - comptime blocks
3633    /// - comptime function calls
3634    fn evaluate_const_init(&mut self, init: InstRef, span: Span) -> CompileResult<Type> {
3635        let init_inst = self.rir.get(init);
3636
3637        match &init_inst.data {
3638            // @import("path") evaluates to Type::Module at compile time
3639            InstData::Intrinsic {
3640                name,
3641                args_start,
3642                args_len,
3643            } => {
3644                if *name == self.known.import {
3645                    // Validate exactly one argument
3646                    if *args_len != 1 {
3647                        return Err(CompileError::new(
3648                            ErrorKind::IntrinsicWrongArgCount {
3649                                name: "import".to_string(),
3650                                expected: 1,
3651                                found: *args_len as usize,
3652                            },
3653                            span,
3654                        ));
3655                    }
3656
3657                    // Accept a string literal or a comptime_str expression.
3658                    let arg_refs = self.rir.get_inst_refs(*args_start, *args_len);
3659                    let import_path = self.resolve_import_path_arg(arg_refs[0])?;
3660
3661                    // Resolve the import path to an absolute file path
3662                    let resolved_path = self.resolve_import_path(&import_path, span)?;
3663
3664                    // Register the module in the registry
3665                    let (module_id, _is_new) = self
3666                        .module_registry
3667                        .get_or_create(import_path, resolved_path);
3668
3669                    Ok(Type::new_module(module_id))
3670                } else {
3671                    // For other intrinsics in const context, we don't support them yet
3672                    let intrinsic_name = self.interner.resolve(name).to_string();
3673                    Err(CompileError::new(
3674                        ErrorKind::ConstExprNotSupported {
3675                            expr_kind: format!("@{} intrinsic", intrinsic_name),
3676                        },
3677                        span,
3678                    ))
3679                }
3680            }
3681
3682            // Integer literals evaluate to i32 (the default integer type)
3683            // Note: RIR doesn't distinguish between integer types at this level;
3684            // type inference happens later. For now, we treat all integer consts as i32.
3685            InstData::IntConst(_) => Ok(Type::I32),
3686
3687            // Boolean literals
3688            InstData::BoolConst(_) => Ok(Type::BOOL),
3689
3690            // Unit literal
3691            InstData::UnitConst => Ok(Type::UNIT),
3692
3693            // String literals
3694            InstData::StringConst(_) => {
3695                // String constants would need the String type
3696                // For now, we don't support them in const context
3697                Err(CompileError::new(
3698                    ErrorKind::ConstExprNotSupported {
3699                        expr_kind: "string literals".to_string(),
3700                    },
3701                    span,
3702                ))
3703            }
3704
3705            // Other expressions are not yet supported in const context
3706            _ => Err(CompileError::new(
3707                ErrorKind::ConstExprNotSupported {
3708                    expr_kind: "this expression".to_string(),
3709                },
3710                span,
3711            )),
3712        }
3713    }
3714}
3715
3716/// Build the trailing note for an unknown-directive error (ADR-0075).
3717///
3718/// Retired directives get a targeted retirement message pointing at the
3719/// ADR that took them out of service. Other names get an edit-distance
3720/// hint when there's a near-match in the recognized set; otherwise no
3721/// note is attached.
3722fn directive_diagnosis_note(name: &str) -> Option<String> {
3723    match name {
3724        "handle" => {
3725            return Some(
3726                "the `@handle` directive was retired in ADR-0075; \
3727                 conform to the `Handle` interface by defining \
3728                 `fn handle(self: Ref(Self)) -> Self` directly"
3729                    .to_string(),
3730            );
3731        }
3732        "copy" => {
3733            return Some(
3734                "the `@copy` directive was retired in ADR-0059; \
3735                 use `@derive(Copy)` instead"
3736                    .to_string(),
3737            );
3738        }
3739        _ => {}
3740    }
3741
3742    const RECOGNIZED: &[&str] = &["allow", "derive", "mark"];
3743    RECOGNIZED
3744        .iter()
3745        .map(|cand| (cand, levenshtein_distance(name, cand)))
3746        .filter(|(_, d)| *d <= 2)
3747        .min_by_key(|(_, d)| *d)
3748        .map(|(cand, _)| format!("did you mean `@{cand}`?"))
3749}
3750
3751/// ADR-0083: edit-distance suggestion for a typo'd marker name.
3752///
3753/// Returns a "did you mean `X`?" hint when there's a near-match in the
3754/// `BUILTIN_MARKERS` registry, otherwise emits a list of the recognized
3755/// markers so users see what's available.
3756fn marker_diagnosis_note(name: &str) -> Option<String> {
3757    let names: Vec<&'static str> = BUILTIN_MARKERS.iter().map(|m| m.name).collect();
3758    if let Some(suggestion) = names
3759        .iter()
3760        .map(|cand| (*cand, levenshtein_distance(name, cand)))
3761        .filter(|(_, d)| *d <= 2)
3762        .min_by_key(|(_, d)| *d)
3763        .map(|(cand, _)| cand)
3764    {
3765        return Some(format!("did you mean `{suggestion}`?"));
3766    }
3767    Some(format!("recognized markers: {}", names.join(", ")))
3768}
3769
3770/// Wagner-Fischer Levenshtein distance. Used by `directive_diagnosis_note`
3771/// to suggest near-matches for typo'd directive names. Both inputs are
3772/// short identifiers (~10 chars), so the O(n·m) cost is negligible and the
3773/// dependency-free implementation is preferable to pulling in `strsim`.
3774fn levenshtein_distance(a: &str, b: &str) -> usize {
3775    let a: Vec<char> = a.chars().collect();
3776    let b: Vec<char> = b.chars().collect();
3777    let (n, m) = (a.len(), b.len());
3778    if n == 0 {
3779        return m;
3780    }
3781    if m == 0 {
3782        return n;
3783    }
3784    let mut prev: Vec<usize> = (0..=m).collect();
3785    let mut curr: Vec<usize> = vec![0; m + 1];
3786    for i in 1..=n {
3787        curr[0] = i;
3788        for j in 1..=m {
3789            let cost = if a[i - 1] == b[j - 1] { 0 } else { 1 };
3790            curr[j] = (curr[j - 1] + 1).min(prev[j] + 1).min(prev[j - 1] + cost);
3791        }
3792        std::mem::swap(&mut prev, &mut curr);
3793    }
3794    prev[m]
3795}
3796
3797#[cfg(test)]
3798mod directive_validation_tests {
3799    use super::{directive_diagnosis_note, levenshtein_distance};
3800
3801    #[test]
3802    fn levenshtein_basics() {
3803        assert_eq!(levenshtein_distance("kitten", "sitting"), 3);
3804        assert_eq!(levenshtein_distance("allow", "allwo"), 2);
3805        assert_eq!(levenshtein_distance("derive", "dervie"), 2);
3806        assert_eq!(levenshtein_distance("derive", "derive"), 0);
3807        assert_eq!(levenshtein_distance("", "abc"), 3);
3808        assert_eq!(levenshtein_distance("abc", ""), 3);
3809    }
3810
3811    #[test]
3812    fn note_for_typo_near_allow() {
3813        let note = directive_diagnosis_note("allwo").expect("near-match should suggest");
3814        assert!(note.contains("@allow"), "got: {note}");
3815    }
3816
3817    #[test]
3818    fn note_for_typo_near_derive() {
3819        let note = directive_diagnosis_note("dervie").expect("near-match should suggest");
3820        assert!(note.contains("@derive"), "got: {note}");
3821    }
3822
3823    #[test]
3824    fn note_for_far_typo_is_none() {
3825        assert!(directive_diagnosis_note("xyzzy").is_none());
3826    }
3827
3828    #[test]
3829    fn note_for_retired_handle_mentions_adr_0075() {
3830        let note = directive_diagnosis_note("handle").expect("retirement note");
3831        assert!(note.contains("ADR-0075"), "got: {note}");
3832        assert!(note.contains("Handle"), "got: {note}");
3833    }
3834
3835    #[test]
3836    fn note_for_retired_copy_mentions_adr_0059() {
3837        let note = directive_diagnosis_note("copy").expect("retirement note");
3838        assert!(note.contains("ADR-0059"), "got: {note}");
3839        assert!(note.contains("@derive(Copy)"), "got: {note}");
3840    }
3841}