Skip to main content

submilli_engine/codegen/
symbol_table.rs

1//! Maps [`MangledName`] to Wasm indices for types, functions, and globals —
2//! both imported symbols and consumer-local declarations.
3
4use std::collections::{BTreeMap, BTreeSet, HashMap};
5
6use wasm_encoder::{HeapType, RefType, ValType};
7
8use crate::codegen::closures::ClosureSig;
9use crate::codegen::internal_failure;
10use crate::codegen::intrinsics::{IntrinsicTypeIndices, intrinsic_supertypes};
11use crate::compiler_error::CompilerFailure;
12use crate::{Dispatch, ExprId, MangledName, Type};
13
14/// One step of a constructor's field-setup sequence, run after `super(...)`
15/// (or at the top of a base class's init fn): own-field initializers and
16/// parameter-property copies, in execution order.
17#[derive(Clone, Debug)]
18pub enum FieldSetup {
19    /// `this.field = <initializer expr>`.
20    Init { field: String, value: ExprId },
21    /// Read the current parameter storage after defaults, including captured cells.
22    ParamCopy {
23        field: String,
24        param: Box<crate::TypedParam>,
25    },
26    /// `this.field = undefined` for an optional field that redeclares an inherited
27    /// one with no initializer of its own. The slot is the parent's, so without
28    /// this it would keep the parent's initialized value.
29    Reset { field: String },
30}
31
32#[derive(Default, Clone, Copy, Debug)]
33pub struct ClassGuardLayout {
34    pub inheritance_depth: u32,
35    pub named_payload_len: u32,
36    pub has_instance_guards: bool,
37}
38
39#[derive(Default, Clone, Debug)]
40pub struct SymbolTable {
41    pub(crate) field_lookup_function: Option<u32>,
42    pub(crate) resultless_functions: BTreeSet<u32>,
43    /// Declared defaults of a host function's Wasm parameters, by position.
44    host_parameter_defaults: BTreeMap<u32, Vec<Option<crate::DefaultValue>>>,
45    pub class_type_parameters: BTreeMap<MangledName, Vec<String>>,
46    pub runtime_generic_functions: BTreeSet<MangledName>,
47    pub field_guard_targets: BTreeMap<u32, Type>,
48    pub type_descriptor_functions: BTreeMap<Type, u32>,
49    pub type_descriptor_globals: BTreeMap<Type, u32>,
50    types: BTreeMap<MangledName, u32>,
51    funcs: BTreeMap<MangledName, u32>,
52    globals: BTreeMap<MangledName, u32>,
53    global_types: BTreeMap<MangledName, Type>,
54    init_guards: BTreeMap<MangledName, crate::codegen::init_guard::InitFlag>,
55    /// The flags of guarded classes, by the index of the top-level statement
56    /// their declaration precedes.
57    class_declaration_flags: BTreeMap<usize, Vec<u32>>,
58    intrinsic_type_indices: Option<IntrinsicTypeIndices>,
59    pub(crate) this_environment_type: Option<u32>,
60    pub(crate) call_metadata_type: Option<u32>,
61    pub(crate) function_argument_metadata: BTreeMap<MangledName, String>,
62    // Per-class WasmGC artifacts, keyed by the class's mangled name. Classes are
63    // nominal (one struct type each), unlike arity-shared object subtypes.
64    class_struct_type_idx: BTreeMap<MangledName, u32>,
65    class_vtable_type_idx: BTreeMap<MangledName, u32>,
66    class_vtable_global_idx: BTreeMap<MangledName, u32>,
67    class_getter_global_idx: BTreeMap<MangledName, u32>,
68    class_setter_global_idx: BTreeMap<MangledName, u32>,
69    class_method_func_idx: BTreeMap<(MangledName, String), u32>,
70    /// Wasm fn index of a class's constructor *init* function — the body that
71    /// initializes fields on an already-allocated instance (self-first ABI), as
72    /// distinct from the allocating entry constructor. `super(...)` calls it.
73    class_ctor_init_func_idx: BTreeMap<MangledName, u32>,
74    /// Wasm fn index of a class method's closure-ABI adapter, for the per-instance
75    /// method closures placed in the object-fields payload (interface-typed dispatch).
76    class_method_adapter_func_idx: BTreeMap<(MangledName, String), u32>,
77    /// Vtable slot index (4 + declaration position) of a class method, for static
78    /// `call_ref` dispatch; keyed for every class in the method's inheritance chain.
79    class_method_slot: BTreeMap<(MangledName, String), u32>,
80    /// Methods whose current callable value lives in the mutable instance payload.
81    class_payload_methods: BTreeSet<(MangledName, String)>,
82    /// Member names this module may write through a structural alias.
83    rewritable_members: crate::codegen::classes::RewritableMembers,
84    /// Wasm fn-type index of a class method's signature, for the `call_ref`.
85    class_method_sig: BTreeMap<(MangledName, String), u32>,
86    /// Physical Wasm signature of a class method's vtable slot, keyed for every
87    /// class in the chain like `class_method_sig`.
88    class_method_abi: BTreeMap<(MangledName, String), MethodSlotAbi>,
89    /// Wasm parameter types of a class's constructor (implicit subclass ctors
90    /// inherit the parent's).
91    class_ctor_abi: BTreeMap<MangledName, Vec<ValType>>,
92    /// Struct slot index (4 + declaration position) of a class field, for
93    /// `struct.get`/`struct.set` on a class-typed receiver.
94    class_field_slot: BTreeMap<(MangledName, String), u32>,
95    /// Read guards, keyed by `(class, field)`. See [`crate::FieldNarrowingCheck`].
96    class_field_narrowing_check: BTreeMap<(MangledName, String), crate::FieldNarrowingCheck>,
97    /// Constructor field-setup steps (parameter-property copies then own-field
98    /// initializers) emitted at the post-`super()` field-setup point.
99    class_field_setup: BTreeMap<MangledName, Vec<FieldSetup>>,
100    vtable_global_idx: BTreeMap<Type, u32>,
101    field_names_global_idx: BTreeMap<Vec<super::field_names::FieldName>, u32>,
102    optional_field_name_type: Option<u32>,
103    field_name_string_global_idx: BTreeMap<String, u32>,
104    // Keyed by ValType so language types sharing a Wasm representation share one box.
105    box_type_idx: HashMap<ValType, u32>,
106    /// Declared Wasm supertype of each struct type the module emits, child →
107    /// parent, for [`ref_fits_slot`](Self::ref_fits_slot).
108    struct_supertype: BTreeMap<u32, u32>,
109    closure_func_type_idx: BTreeMap<ClosureSig, u32>,
110    closure_struct_type_idx: BTreeMap<ClosureSig, u32>,
111    closure_coercions: BTreeMap<ClosureSig, u32>,
112    closure_coercion_vtable_type: Option<u32>,
113    env_type_idx: BTreeMap<ExprId, u32>,
114    closure_func_idx: BTreeMap<ExprId, u32>,
115    closure_vtable_global_idx: Option<u32>,
116    /// The sole exception tag's index, recorded at import emission.
117    error_tag_idx: Option<u32>,
118    adapter_func_idx: BTreeMap<MangledName, u32>,
119    adapter_closure_global_idx: BTreeMap<MangledName, u32>,
120    /// The closure caches typed `anyref` because the function's package exports
121    /// them, so its consumers share one closure; see `function_adapters`.
122    shared_closure_globals: BTreeSet<MangledName>,
123    /// Runtime validator helpers keyed by recursive alias or interface back-edges.
124    runtime_validator_idx: BTreeMap<Type, u32>,
125    generic_runtime_validators: BTreeMap<MangledName, (Vec<String>, u32)>,
126    instance_field_guards: BTreeMap<(Type, MangledName, String), u32>,
127    class_guard_layout: BTreeMap<MangledName, ClassGuardLayout>,
128    // Closures, adapters, and direct-dispatch wrappers are NOT in this map.
129    top_level_fns: BTreeMap<MangledName, TopLevelFn>,
130    iface_dispatch: BTreeMap<MangledName, Dispatch>,
131    /// The JavaScript string form of each static-dispatch interface's binding,
132    /// which names the object the binding reads as when used as a value.
133    static_value_tags: BTreeMap<MangledName, String>,
134    /// Physical signature of each Direct/Static-dispatch interface wrapper,
135    /// keyed by its `iface#method` dispatch key.
136    iface_method_abi: BTreeMap<MangledName, MethodSlotAbi>,
137    // Members declared `intrinsic` — codegen emits them inline; nothing imports.
138    intrinsic_members: BTreeSet<MangledName>,
139}
140
141#[derive(Clone, Debug)]
142pub struct TopLevelFn {
143    pub wasm_idx: u32,
144    pub params: Vec<Type>,
145    pub ret: Type,
146    /// `true` for host modules whose Wasm ABI uses raw `(ref $rawString)` instead of
147    /// `(ref $string)`; drives per-arg extract and post-call rewrap in `emit_direct_call`.
148    pub is_host: bool,
149}
150
151/// The physical Wasm signature of a dispatched method — a class-method vtable
152/// slot, or a Direct/Static-dispatch interface wrapper — captured where the
153/// signature is built rather than re-derived at each call site.
154///
155/// A class slot's signature belongs to the ancestor that introduced it, and
156/// erases class types and type variables (see
157/// [`slot_value_type`](SymbolTable::slot_value_type)), so it can differ from
158/// what an overriding method declares; an interface wrapper's signature erases
159/// the interface's type parameters the same way. Call sites coerce arguments
160/// into [`params`](Self::params) and cast [`ret`](Self::ret) back to the
161/// call-site type; an overriding body binds `params` and returns `ret`
162/// whatever its own annotations say.
163#[derive(Clone, Debug, Default, PartialEq)]
164pub struct MethodSlotAbi {
165    /// User parameters; the leading `self` is excluded.
166    pub params: Vec<ValType>,
167    /// `None` for a void slot.
168    pub ret: Option<ValType>,
169}
170
171/// Whether a type occupies the erased (boxed) object slot rather than its own
172/// concrete lowering. Class types are included: an erased slot must lower the
173/// same way regardless of whether the class's struct type has been recorded yet
174/// — imported classes are reconstructed before local slot signatures are built,
175/// local classes after, and box cells are collected before either — and
176/// regardless of which package is compiling it. A union erases whole if any
177/// member does; a fully concrete union already lowers identically everywhere.
178pub fn is_erased(ty: &Type) -> bool {
179    match ty.peel() {
180        Type::TypeVar(_) | Type::GenericParam { .. } | Type::ClassRef { .. } => true,
181        Type::Union(members) => members.iter().any(is_erased),
182        _ => false,
183    }
184}
185
186/// The *lowering* question — whether a Wasm slot of this type can hold null.
187/// See [`may_hold_null`] for the language-level one.
188pub(crate) fn is_nullable_ref(val: ValType) -> bool {
189    matches!(val, ValType::Ref(RefType { nullable: true, .. }))
190}
191
192/// True iff `ty`'s value space contains `null`.
193///
194/// The *semantic* question, deliberately not `is_nullable_ref(value_type(ty))`:
195/// an `InterfaceRef` and an unrecorded `ClassRef` lower to a nullable
196/// `(ref null $Object)` without admitting `null` at the language level, and the
197/// `ClassRef` lowering is phase-dependent besides. Codegen reads this wherever
198/// it has to decide "can this value be null at runtime" — the null-aware `===`
199/// dispatch, `JSON.stringify`, and the object-shape `toJson` emitter.
200pub(crate) fn may_hold_null(ty: &Type) -> bool {
201    match ty.peel() {
202        // A recursion back-edge is a name codegen has no body for, and the alias
203        // it names often does list `null` (`type J = number | null | Wrap[]`).
204        Type::Null
205        | Type::Void
206        | Type::Unknown
207        | Type::TypeVar(_)
208        | Type::GenericParam { .. }
209        | Type::AliasRef { .. } => true,
210        Type::Union(members) => members.iter().any(may_hold_null),
211        _ => false,
212    }
213}
214
215impl SymbolTable {
216    pub fn new() -> Self {
217        Self::default()
218    }
219
220    pub fn type_idx(&self, mangled: &MangledName) -> Option<u32> {
221        self.types.get(mangled).copied()
222    }
223
224    pub fn func_idx(&self, mangled: &MangledName) -> Option<u32> {
225        self.funcs.get(mangled).copied()
226    }
227
228    pub fn global_idx(&self, mangled: &MangledName) -> Option<u32> {
229        self.globals.get(mangled).copied()
230    }
231
232    pub fn global_type(&self, mangled: &MangledName) -> Option<&Type> {
233        self.global_types.get(mangled)
234    }
235
236    pub fn init_guard(
237        &self,
238        global: &MangledName,
239    ) -> Option<&crate::codegen::init_guard::InitFlag> {
240        self.init_guards.get(global)
241    }
242
243    pub fn class_declaration_flags(&self, index: usize) -> &[u32] {
244        self.class_declaration_flags
245            .get(&index)
246            .map_or(&[], Vec::as_slice)
247    }
248
249    pub fn prelude_func_idx(&self, symbol: &str) -> Option<u32> {
250        self.func_idx(&crate::mangle::prelude(symbol))
251    }
252
253    pub fn prelude_global_idx(&self, symbol: &str) -> Option<u32> {
254        self.global_idx(&crate::mangle::prelude(symbol))
255    }
256
257    pub fn string_type_idx(&self) -> Option<u32> {
258        self.intrinsic_type_indices.map(|i| i.string)
259    }
260
261    pub fn raw_string_type_idx(&self) -> Option<u32> {
262        self.intrinsic_type_indices.map(|i| i.raw_string)
263    }
264
265    pub fn bigint_type_idx(&self) -> Option<u32> {
266        self.intrinsic_type_indices.map(|i| i.bigint)
267    }
268
269    pub fn raw_bigint_type_idx(&self) -> Option<u32> {
270        self.intrinsic_type_indices.map(|i| i.raw_bigint)
271    }
272
273    pub fn regex_type_idx(&self) -> Option<u32> {
274        self.intrinsic_type_indices.map(|i| i.regex)
275    }
276
277    pub fn regex_match_type_idx(&self) -> Option<u32> {
278        self.intrinsic_type_indices.map(|i| i.regex_match)
279    }
280
281    pub fn regex_capture_array_type_idx(&self) -> Option<u32> {
282        self.intrinsic_type_indices.map(|i| i.regex_capture_array)
283    }
284
285    pub fn regex_match_box_type_idx(&self) -> Option<u32> {
286        self.intrinsic_type_indices.map(|i| i.regex_match_box)
287    }
288
289    pub fn boxed_number_type_idx(&self) -> Option<u32> {
290        self.intrinsic_type_indices.map(|i| i.boxed_number)
291    }
292
293    pub fn boxed_boolean_type_idx(&self) -> Option<u32> {
294        self.intrinsic_type_indices.map(|i| i.boxed_boolean)
295    }
296
297    pub fn raw_array_type_idx(&self) -> Option<u32> {
298        self.intrinsic_type_indices.map(|i| i.raw_array)
299    }
300
301    pub fn field_names_type_idx(&self) -> Option<u32> {
302        self.intrinsic_type_indices.map(|i| i.field_names)
303    }
304
305    pub fn object_shape_type_idx(&self) -> Option<u32> {
306        self.intrinsic_type_indices.map(|i| i.object_shape)
307    }
308
309    pub fn array_type_idx(&self) -> Option<u32> {
310        self.intrinsic_type_indices.map(|i| i.array)
311    }
312
313    pub fn raw_uint8_array_type_idx(&self) -> Option<u32> {
314        self.intrinsic_type_indices.map(|i| i.raw_uint8_array)
315    }
316
317    pub fn uint8_array_type_idx(&self) -> Option<u32> {
318        self.intrinsic_type_indices.map(|i| i.uint8_array)
319    }
320
321    pub fn closure_type_idx(&self) -> Option<u32> {
322        self.intrinsic_type_indices.map(|i| i.closure)
323    }
324
325    pub fn intrinsic_type_indices(&self) -> Option<IntrinsicTypeIndices> {
326        self.intrinsic_type_indices
327    }
328
329    pub fn object_subtype_idx(&self, ty: &Type) -> Option<u32> {
330        match ty {
331            Type::Object { .. } => self.object_shape_type_idx(),
332            _ => None,
333        }
334    }
335
336    pub fn class_struct_type_idx(&self, mangled: &MangledName) -> Option<u32> {
337        self.class_struct_type_idx.get(mangled).copied()
338    }
339
340    pub fn class_vtable_type_idx(&self, mangled: &MangledName) -> Option<u32> {
341        self.class_vtable_type_idx.get(mangled).copied()
342    }
343
344    pub fn class_vtable_global_idx(&self, mangled: &MangledName) -> Option<u32> {
345        self.class_vtable_global_idx.get(mangled).copied()
346    }
347
348    pub fn class_getter_global_idx(&self, mangled: &MangledName) -> Option<u32> {
349        self.class_getter_global_idx.get(mangled).copied()
350    }
351
352    pub fn class_setter_global_idx(&self, mangled: &MangledName) -> Option<u32> {
353        self.class_setter_global_idx.get(mangled).copied()
354    }
355
356    pub fn class_method_func_idx(&self, class: &MangledName, method: &str) -> Option<u32> {
357        self.class_method_func_idx
358            .get(&(class.clone(), method.to_string()))
359            .copied()
360    }
361
362    pub fn class_ctor_init_func_idx(&self, class: &MangledName) -> Option<u32> {
363        self.class_ctor_init_func_idx.get(class).copied()
364    }
365
366    pub fn class_method_adapter_func_idx(&self, class: &MangledName, method: &str) -> Option<u32> {
367        self.class_method_adapter_func_idx
368            .get(&(class.clone(), method.to_string()))
369            .copied()
370    }
371
372    pub fn class_method_slot(&self, class: &MangledName, method: &str) -> Option<u32> {
373        self.class_method_slot
374            .get(&(class.clone(), method.to_string()))
375            .copied()
376    }
377
378    pub fn is_class_payload_method(&self, class: &MangledName, method: &str) -> bool {
379        self.class_payload_methods
380            .contains(&(class.clone(), method.to_string()))
381    }
382
383    pub fn class_method_sig(&self, class: &MangledName, method: &str) -> Option<u32> {
384        self.class_method_sig
385            .get(&(class.clone(), method.to_string()))
386            .copied()
387    }
388
389    pub fn class_method_abi(&self, class: &MangledName, method: &str) -> Option<&MethodSlotAbi> {
390        self.class_method_abi
391            .get(&(class.clone(), method.to_string()))
392    }
393
394    pub fn class_ctor_abi(&self, class: &MangledName) -> Option<&[ValType]> {
395        self.class_ctor_abi.get(class).map(Vec::as_slice)
396    }
397
398    pub fn class_field_slot(&self, class: &MangledName, field: &str) -> Option<u32> {
399        self.class_field_slot
400            .get(&(class.clone(), field.to_string()))
401            .copied()
402    }
403
404    /// `None` for every ordinary field. See [`crate::FieldNarrowingCheck`].
405    pub fn class_field_narrowing_check(
406        &self,
407        class: &MangledName,
408        field: &str,
409    ) -> Option<&crate::FieldNarrowingCheck> {
410        self.class_field_narrowing_check
411            .get(&(class.clone(), field.to_string()))
412    }
413
414    pub fn class_field_narrowing_checks<'a>(
415        &'a self,
416        field: &'a str,
417    ) -> impl Iterator<Item = (&'a MangledName, &'a crate::FieldNarrowingCheck)> + 'a {
418        self.class_field_narrowing_check
419            .iter()
420            .filter(move |((_, name), _)| name == field)
421            .map(|((class, _), check)| (class, check))
422    }
423
424    pub fn class_field_setup(&self, class: &MangledName) -> Option<&[FieldSetup]> {
425        self.class_field_setup.get(class).map(Vec::as_slice)
426    }
427
428    pub fn vtable_global_idx(&self, ty: &Type) -> Option<u32> {
429        self.vtable_global_idx.get(ty).copied()
430    }
431
432    pub fn record_optional_field_name_type(&mut self, index: u32) {
433        self.optional_field_name_type = Some(index);
434    }
435
436    pub fn optional_field_name_type(&self) -> Result<u32, CompilerFailure> {
437        self.optional_field_name_type
438            .ok_or_else(|| internal_failure("optional field-name type was not declared"))
439    }
440
441    pub fn field_names_global_idx(
442        &self,
443        field_names: &[super::field_names::FieldName],
444    ) -> Option<u32> {
445        self.field_names_global_idx.get(field_names).copied()
446    }
447
448    pub fn field_name_string_global_idx(&self, name: &str) -> Option<u32> {
449        self.field_name_string_global_idx.get(name).copied()
450    }
451
452    /// Box cells are keyed on [`slot_value_type`](Self::slot_value_type) so the
453    /// key a binding registers during collection still resolves once class
454    /// struct types are recorded. A cell for an erased type therefore holds
455    /// `(ref null $Object)`; reads out of it need `emit_unerase`.
456    pub fn box_type_idx(
457        &self,
458        ty: &Type,
459    ) -> Result<Option<u32>, crate::compiler_error::CompilerFailure> {
460        Ok(self.box_type_idx.get(&self.slot_value_type(ty)?).copied())
461    }
462
463    /// A box struct is the `(ref $box_T)` wrapper a captured-and-mutated binding
464    /// is stored behind. Returns the payload type of `idx`, or `None` if `idx`
465    /// is not one — so codegen can recognise a slot that holds a box rather than
466    /// the value, and know what a `struct.get` off it leaves on the stack.
467    ///
468    /// The reverse scan is unambiguous because `box_types::collect` dedups by
469    /// payload before recording, so one index is registered per payload type.
470    pub fn box_payload_type(&self, idx: u32) -> Option<ValType> {
471        self.box_type_idx
472            .iter()
473            .find(|&(_, &v)| v == idx)
474            .map(|(payload, _)| *payload)
475    }
476
477    pub fn closure_func_type_idx(&self, sig: ClosureSig) -> Option<u32> {
478        self.closure_func_type_idx.get(&sig).copied()
479    }
480
481    pub fn record_closure_coercion_vtable_type(&mut self, index: u32) {
482        self.closure_coercion_vtable_type = Some(index);
483    }
484
485    pub fn closure_coercion_vtable_type(
486        &self,
487    ) -> Result<u32, crate::compiler_error::CompilerFailure> {
488        self.closure_coercion_vtable_type.ok_or_else(|| {
489            crate::codegen::internal_failure("closure coercion vtable is not registered")
490        })
491    }
492
493    pub fn closure_signatures(&self) -> impl Iterator<Item = ClosureSig> + '_ {
494        self.closure_struct_type_idx.keys().copied()
495    }
496
497    pub fn record_closure_coercion(&mut self, target: ClosureSig, index: u32) {
498        self.closure_coercions.insert(target, index);
499    }
500
501    pub fn closure_coercion(&self, target: ClosureSig) -> Option<u32> {
502        self.closure_coercions.get(&target).copied()
503    }
504
505    pub fn closure_struct_type_idx(&self, sig: ClosureSig) -> Option<u32> {
506        self.closure_struct_type_idx.get(&sig).copied()
507    }
508
509    pub fn env_type_idx(&self, expr_id: ExprId) -> Option<u32> {
510        self.env_type_idx.get(&expr_id).copied()
511    }
512
513    pub fn closure_func_idx(&self, expr_id: ExprId) -> Option<u32> {
514        self.closure_func_idx.get(&expr_id).copied()
515    }
516
517    pub fn closure_vtable_global_idx(&self) -> Option<u32> {
518        self.closure_vtable_global_idx
519    }
520
521    pub fn adapter_func_idx(&self, mangled: &MangledName) -> Option<u32> {
522        self.adapter_func_idx.get(mangled).copied()
523    }
524
525    pub fn adapter_closure_global_idx(&self, mangled: &MangledName) -> Option<u32> {
526        self.adapter_closure_global_idx.get(mangled).copied()
527    }
528
529    pub fn is_shared_closure_global(&self, mangled: &MangledName) -> bool {
530        self.shared_closure_globals.contains(mangled)
531    }
532
533    /// Whether a `value`-typed ref already satisfies a `slot` by WasmGC
534    /// subtyping, so a coercion needs no instruction.
535    ///
536    /// Answers `false` whenever the relation isn't known — an unrecorded
537    /// supertype edge, an abstract heap type — leaving the caller to emit a
538    /// `ref.cast`, which is valid for any two types sharing a hierarchy. Wrong
539    /// in that direction costs an always-succeeding cast; wrong in the other
540    /// direction is a module the validator rejects.
541    pub fn ref_fits_slot(&self, value: RefType, slot: RefType) -> bool {
542        if value.nullable && !slot.nullable {
543            return false;
544        }
545        let (HeapType::Concrete(value_idx), HeapType::Concrete(slot_idx)) =
546            (value.heap_type, slot.heap_type)
547        else {
548            return value.heap_type == slot.heap_type;
549        };
550        let mut current = value_idx;
551        while current != slot_idx {
552            // Wasm requires a supertype to be declared before its subtypes, so
553            // the walk strictly descends and terminates whatever the map holds.
554            match self.struct_supertype.get(&current) {
555                Some(&parent) if parent < current => current = parent,
556                _ => return false,
557            }
558        }
559        true
560    }
561
562    pub fn record_type(&mut self, mangled: MangledName, idx: u32) {
563        self.types.insert(mangled, idx);
564    }
565
566    /// Also seeds the intrinsic hierarchy: those edges are fixed by the indices,
567    /// so they can neither be known before this call nor go stale after it.
568    pub fn set_intrinsic_type_indices(&mut self, indices: IntrinsicTypeIndices) {
569        self.intrinsic_type_indices = Some(indices);
570        for (child, parent) in intrinsic_supertypes(indices) {
571            self.record_struct_supertype(child, parent);
572        }
573    }
574
575    pub fn record_struct_supertype(&mut self, child: u32, parent: u32) {
576        self.struct_supertype.insert(child, parent);
577    }
578
579    pub fn record_class_struct_type(&mut self, mangled: MangledName, idx: u32) {
580        self.class_struct_type_idx.insert(mangled, idx);
581    }
582
583    pub fn record_class_vtable_type(&mut self, mangled: MangledName, idx: u32) {
584        self.class_vtable_type_idx.insert(mangled, idx);
585    }
586
587    pub fn record_class_vtable_global(&mut self, mangled: MangledName, idx: u32) {
588        self.class_vtable_global_idx.insert(mangled, idx);
589    }
590
591    pub fn record_class_getter_global(&mut self, mangled: MangledName, idx: u32) {
592        self.class_getter_global_idx.insert(mangled, idx);
593    }
594
595    pub fn record_class_setter_global(&mut self, mangled: MangledName, idx: u32) {
596        self.class_setter_global_idx.insert(mangled, idx);
597    }
598
599    pub fn record_class_method_func(&mut self, class: MangledName, method: String, idx: u32) {
600        self.class_method_func_idx.insert((class, method), idx);
601    }
602
603    pub fn record_class_ctor_init_func(&mut self, class: MangledName, idx: u32) {
604        self.class_ctor_init_func_idx.insert(class, idx);
605    }
606
607    pub fn record_class_method_adapter_func(
608        &mut self,
609        class: MangledName,
610        method: String,
611        idx: u32,
612    ) {
613        self.class_method_adapter_func_idx
614            .insert((class, method), idx);
615    }
616
617    pub fn record_class_method_slot(&mut self, class: MangledName, method: String, slot: u32) {
618        self.class_method_slot.insert((class, method), slot);
619    }
620
621    pub(crate) fn record_host_parameter_defaults(
622        &mut self,
623        func_idx: u32,
624        defaults: Vec<Option<crate::DefaultValue>>,
625    ) {
626        self.host_parameter_defaults.insert(func_idx, defaults);
627    }
628
629    /// The declared defaults for a call to host function `func_idx`, aligned
630    /// to its `arg_count` arguments: entry `i` belongs to argument `i`.
631    ///
632    /// The arguments fill the trailing parameters, so any leading parameter
633    /// without an argument (a method's receiver, already on the stack) is
634    /// skipped. A function without recorded defaults has none.
635    pub(crate) fn host_call_defaults(
636        &self,
637        func_idx: u32,
638        arg_count: usize,
639    ) -> &[Option<crate::DefaultValue>] {
640        let defaults = self
641            .host_parameter_defaults
642            .get(&func_idx)
643            .map_or(&[][..], Vec::as_slice);
644        let receivers = defaults.len().saturating_sub(arg_count);
645        defaults.get(receivers..).unwrap_or_default()
646    }
647
648    pub fn record_class_payload_method(&mut self, class: MangledName, method: String) {
649        self.class_payload_methods.insert((class, method));
650    }
651
652    pub(crate) fn record_rewritable_members(
653        &mut self,
654        members: crate::codegen::classes::RewritableMembers,
655    ) {
656        self.rewritable_members = members;
657    }
658
659    /// A payload method, local or imported, that this module may overwrite
660    /// through a structural alias, so a direct call must read the payload rather
661    /// than the class vtable. Every other call keeps the cheaper vtable dispatch.
662    pub fn is_rewritable_class_method(&self, class: &MangledName, method: &str) -> bool {
663        self.rewritable_members.includes(method) && self.is_class_payload_method(class, method)
664    }
665
666    pub fn record_class_method_sig(&mut self, class: MangledName, method: String, sig: u32) {
667        self.class_method_sig.insert((class, method), sig);
668    }
669
670    pub fn record_class_method_abi(
671        &mut self,
672        class: MangledName,
673        method: String,
674        abi: MethodSlotAbi,
675    ) {
676        self.class_method_abi.insert((class, method), abi);
677    }
678
679    pub fn record_class_ctor_abi(&mut self, class: MangledName, params: Vec<ValType>) {
680        self.class_ctor_abi.insert(class, params);
681    }
682
683    pub fn record_class_field_narrowing_check(
684        &mut self,
685        class: MangledName,
686        field: String,
687        check: crate::FieldNarrowingCheck,
688    ) {
689        self.class_field_narrowing_check
690            .insert((class, field), check);
691    }
692
693    pub fn record_class_field_slot(&mut self, class: MangledName, field: String, slot: u32) {
694        self.class_field_slot.insert((class, field), slot);
695    }
696
697    pub fn record_class_field_setup(&mut self, class: MangledName, setup: Vec<FieldSetup>) {
698        self.class_field_setup.insert(class, setup);
699    }
700
701    pub fn record_vtable_global(&mut self, ty: Type, idx: u32) {
702        self.vtable_global_idx.insert(ty, idx);
703    }
704
705    pub fn record_field_names_global(
706        &mut self,
707        field_names: Vec<super::field_names::FieldName>,
708        idx: u32,
709    ) {
710        self.field_names_global_idx.insert(field_names, idx);
711    }
712
713    pub fn record_field_name_string_global(&mut self, name: String, idx: u32) {
714        self.field_name_string_global_idx.insert(name, idx);
715    }
716
717    pub fn record_box_type(&mut self, value_type: ValType, idx: u32) {
718        self.box_type_idx.insert(value_type, idx);
719    }
720
721    pub fn record_closure_func_type(&mut self, sig: ClosureSig, idx: u32) {
722        self.closure_func_type_idx.insert(sig, idx);
723    }
724
725    pub fn record_closure_struct_type(&mut self, sig: ClosureSig, idx: u32) {
726        self.closure_struct_type_idx.insert(sig, idx);
727    }
728
729    pub fn record_env_type(&mut self, expr_id: ExprId, idx: u32) {
730        self.env_type_idx.insert(expr_id, idx);
731    }
732
733    pub fn record_closure_func_idx(&mut self, expr_id: ExprId, idx: u32) {
734        self.closure_func_idx.insert(expr_id, idx);
735    }
736
737    pub fn set_closure_vtable_global(&mut self, idx: u32) {
738        self.closure_vtable_global_idx = Some(idx);
739    }
740
741    // Kept as Option so call sites pattern-match consistently with other optional resources.
742    pub fn error_tag_idx(&self) -> Option<u32> {
743        self.error_tag_idx
744    }
745
746    pub fn set_error_tag_idx(&mut self, idx: u32) {
747        self.error_tag_idx = Some(idx);
748    }
749
750    pub fn record_adapter_func_idx(&mut self, mangled: MangledName, idx: u32) {
751        self.adapter_func_idx.insert(mangled, idx);
752    }
753
754    pub fn record_adapter_closure_global_idx(&mut self, mangled: MangledName, idx: u32) {
755        self.adapter_closure_global_idx.insert(mangled, idx);
756    }
757
758    pub fn record_shared_closure_global_idx(&mut self, mangled: MangledName, idx: u32) {
759        self.shared_closure_globals.insert(mangled.clone());
760        self.adapter_closure_global_idx.insert(mangled, idx);
761    }
762
763    pub fn record_class_guard_layout(
764        &mut self,
765        class: MangledName,
766        parent: Option<&MangledName>,
767        named_len: u32,
768        guarded: bool,
769    ) -> Result<(), CompilerFailure> {
770        let depth = match parent {
771            Some(parent) => self
772                .recorded_class_guard_layout(parent)?
773                .inheritance_depth
774                .checked_add(1)
775                .ok_or_else(|| internal_failure("class inheritance depth overflowed"))?,
776            None => 0,
777        };
778        self.class_guard_layout.insert(
779            class,
780            ClassGuardLayout {
781                inheritance_depth: depth,
782                named_payload_len: named_len,
783                has_instance_guards: guarded,
784            },
785        );
786        Ok(())
787    }
788
789    /// Defaults an unrecorded class to no guards. Only the field-guard emitters
790    /// still rely on this until SUB-633 item 17; everything else uses
791    /// [`Self::recorded_class_guard_layout`].
792    pub fn class_guard_layout(&self, class: &MangledName) -> ClassGuardLayout {
793        self.class_guard_layout
794            .get(class)
795            .copied()
796            .unwrap_or_default()
797    }
798
799    /// Guard layout of a class this module lays out or reconstructs, which is
800    /// recorded before any use.
801    pub fn recorded_class_guard_layout(
802        &self,
803        class: &MangledName,
804    ) -> Result<ClassGuardLayout, CompilerFailure> {
805        self.class_guard_layout.get(class).copied().ok_or_else(|| {
806            internal_failure(format!(
807                "class `{}` has no recorded guard layout",
808                class.as_str()
809            ))
810        })
811    }
812
813    pub fn record_instance_field_guard(
814        &mut self,
815        class: Type,
816        declaration: MangledName,
817        field: String,
818        function: u32,
819    ) {
820        self.instance_field_guards
821            .insert((class, declaration, field), function);
822    }
823
824    pub fn instance_field_guards<'a>(
825        &'a self,
826        class: &'a Type,
827    ) -> impl Iterator<Item = (&'a MangledName, &'a str, u32)> + 'a {
828        self.instance_field_guards
829            .iter()
830            .filter(move |((ty, _, _), _)| ty == class)
831            .map(|((_, declaration, field), function)| (declaration, field.as_str(), *function))
832    }
833
834    pub fn record_runtime_validator(&mut self, key: Type, idx: u32) {
835        if let Type::AliasRef { mangled, args, .. } | Type::InterfaceRef { mangled, args, .. } =
836            &key
837            && let Some(names) = generic_parameter_names(args)
838        {
839            self.generic_runtime_validators
840                .insert(mangled.clone(), (names, idx));
841        }
842        self.runtime_validator_idx.insert(key, idx);
843    }
844
845    /// Function index of a recursive runtime validator keyed by an alias or
846    /// interface back-edge.
847    pub fn runtime_validator_idx(&self, key: &Type) -> Option<u32> {
848        self.runtime_validator_idx
849            .get(key)
850            .copied()
851            .or_else(|| self.generic_runtime_validator(key).map(|(_, idx)| idx))
852    }
853
854    pub fn generic_runtime_validator(&self, key: &Type) -> Option<(&[String], u32)> {
855        let (Type::AliasRef {
856            mangled: identity, ..
857        }
858        | Type::InterfaceRef {
859            mangled: identity, ..
860        }) = key.peel()
861        else {
862            return None;
863        };
864        let (names, index) = self.generic_runtime_validators.get(identity)?;
865        if self
866            .runtime_validator_idx
867            .get(key)
868            .is_some_and(|exact| exact != index)
869        {
870            return None;
871        }
872        Some((names.as_slice(), *index))
873    }
874
875    pub fn record_func(&mut self, mangled: MangledName, idx: u32) {
876        self.funcs.insert(mangled, idx);
877    }
878
879    pub fn record_global(&mut self, mangled: MangledName, idx: u32) {
880        self.globals.insert(mangled, idx);
881    }
882
883    /// A guarded module global's initialization flag; see `init_guard`.
884    pub fn record_init_guard(
885        &mut self,
886        global: MangledName,
887        flag: crate::codegen::init_guard::InitFlag,
888    ) {
889        self.init_guards.insert(global, flag);
890    }
891
892    pub fn record_class_declaration_flag(&mut self, index: usize, flag_idx: u32) {
893        self.class_declaration_flags
894            .entry(index)
895            .or_default()
896            .push(flag_idx);
897    }
898
899    /// Language globals retain their declared type for checked narrowed reads.
900    /// Runtime metadata globals only need an index.
901    pub fn record_typed_global(&mut self, mangled: MangledName, idx: u32, ty: Type) {
902        self.global_types.insert(mangled.clone(), ty);
903        self.record_global(mangled, idx);
904    }
905
906    /// Writes both the wasm function index and the static-call registry entry;
907    /// splitting them invited callers to drop one half by accident.
908    pub fn record_imported_fn(
909        &mut self,
910        mangled: MangledName,
911        idx: u32,
912        params: Vec<Type>,
913        ret: Type,
914        is_host: bool,
915    ) {
916        self.funcs.insert(mangled.clone(), idx);
917        self.top_level_fns.insert(
918            mangled,
919            TopLevelFn {
920                wasm_idx: idx,
921                params,
922                ret,
923                is_host,
924            },
925        );
926    }
927
928    pub fn record_local_fn(
929        &mut self,
930        mangled: MangledName,
931        idx: u32,
932        params: Vec<Type>,
933        ret: Type,
934    ) -> Result<(), CompilerFailure> {
935        // Guard against an accidental mangle/source-name swap.
936        if !mangled.as_str().contains(crate::mangle::SEP) {
937            return Err(internal_failure(format!(
938                "local function `{}` has no mangled package prefix",
939                mangled.as_str()
940            )));
941        }
942        self.funcs.insert(mangled.clone(), idx);
943        self.top_level_fns.insert(
944            mangled,
945            TopLevelFn {
946                wasm_idx: idx,
947                params,
948                ret,
949                is_host: false,
950            },
951        );
952        Ok(())
953    }
954
955    pub fn top_level_fn(&self, mangled: &MangledName) -> Option<&TopLevelFn> {
956        self.top_level_fns.get(mangled)
957    }
958
959    pub fn record_iface_dispatch(&mut self, iface: MangledName, dispatch: Dispatch) {
960        self.iface_dispatch.insert(iface, dispatch);
961    }
962
963    pub fn record_static_value_tag(&mut self, iface: MangledName, tag: String) {
964        self.static_value_tags.insert(iface, tag);
965    }
966
967    pub fn static_value_tag(&self, iface: &MangledName) -> Option<&str> {
968        self.static_value_tags.get(iface).map(String::as_str)
969    }
970
971    pub fn iface_dispatch(&self, iface: &MangledName) -> Option<Dispatch> {
972        self.iface_dispatch.get(iface).copied()
973    }
974
975    pub fn record_iface_method_abi(&mut self, method_key: MangledName, abi: MethodSlotAbi) {
976        self.iface_method_abi.insert(method_key, abi);
977    }
978
979    pub fn iface_method_abi(&self, method_key: &MangledName) -> Option<&MethodSlotAbi> {
980        self.iface_method_abi.get(method_key)
981    }
982
983    pub fn record_intrinsic_member(&mut self, mangled: MangledName) {
984        self.intrinsic_members.insert(mangled);
985    }
986
987    pub fn is_intrinsic_member(&self, mangled: &MangledName) -> bool {
988        self.intrinsic_members.contains(mangled)
989    }
990
991    pub fn value_type(&self, ty: &Type) -> Result<ValType, crate::compiler_error::CompilerFailure> {
992        let ty = ty.peel();
993        Ok(match ty {
994            Type::Number | Type::NumberLiteral(_) => ValType::F64,
995            Type::Boolean | Type::BooleanLiteral(_) => ValType::I32,
996            Type::String | Type::StringLiteral(_) => {
997                let idx = self.string_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::String requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
998                ValType::Ref(RefType {
999                    nullable: false,
1000                    heap_type: HeapType::Concrete(idx),
1001                })
1002            }
1003            Type::BigInt | Type::BigIntLiteral(_) => {
1004                let idx = self.bigint_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::BigInt requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1005                ValType::Ref(RefType {
1006                    nullable: false,
1007                    heap_type: HeapType::Concrete(idx),
1008                })
1009            }
1010            Type::Object { .. } => {
1011                // All objects lower to `$ObjectShape` (not the arity-specific subtype):
1012                // sibling per-arity subtypes are not Wasm subtypes of each other, so
1013                // width subtyping and object-only unions require `$ObjectShape` as the
1014                // common supertype.
1015                let idx = self
1016                    .intrinsic_type_indices()
1017                    .ok_or_else(|| {
1018                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1019                    })?
1020                    .object_shape;
1021                ValType::Ref(RefType {
1022                    nullable: false,
1023                    heap_type: HeapType::Concrete(idx),
1024                })
1025            }
1026            Type::Array(_) => {
1027                let idx = self.array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Array requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1028                ValType::Ref(RefType {
1029                    nullable: false,
1030                    heap_type: HeapType::Concrete(idx),
1031                })
1032            }
1033            Type::Tuple(_) => {
1034                // Tuples lower to `$Array` — they are arrays at runtime; positional
1035                // element types are tracked by the typechecker only.
1036                let idx = self.array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Tuple requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1037                ValType::Ref(RefType {
1038                    nullable: false,
1039                    heap_type: HeapType::Concrete(idx),
1040                })
1041            }
1042            Type::Uint8Array => {
1043                let idx = self.uint8_array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Uint8Array requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1044                ValType::Ref(RefType {
1045                    nullable: false,
1046                    heap_type: HeapType::Concrete(idx),
1047                })
1048            }
1049            Type::TypeVar(_) | Type::GenericParam { .. } | Type::Unknown | Type::Void => {
1050                // Erased-generic and dynamic slots lower to nullable `(ref null $Object)`;
1051                // nullable so `null` literals can flow in via WasmGC subtyping.
1052                let object_idx = self
1053                    .intrinsic_type_indices()
1054                    .ok_or_else(|| {
1055                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1056                    })?
1057                    .object;
1058                ValType::Ref(RefType {
1059                    nullable: true,
1060                    heap_type: HeapType::Concrete(object_idx),
1061                })
1062            }
1063            Type::Function { .. } => {
1064                let sig = crate::codegen::closures::classify(ty)?;
1065                let idx = self.closure_struct_type_idx(sig).ok_or_else(|| {
1066                    crate::codegen::internal_failure(format!(
1067                        "closure signature {sig:?} is not registered"
1068                    ))
1069                })?;
1070                ValType::Ref(RefType {
1071                    nullable: false,
1072                    heap_type: HeapType::Concrete(idx),
1073                })
1074            }
1075            Type::Null => {
1076                let object_idx = self
1077                    .intrinsic_type_indices()
1078                    .ok_or_else(|| {
1079                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1080                    })?
1081                    .object;
1082                ValType::Ref(RefType {
1083                    nullable: true,
1084                    heap_type: HeapType::Concrete(object_idx),
1085                })
1086            }
1087            Type::Undefined => {
1088                let intrinsic = self.intrinsic_type_indices().ok_or_else(|| {
1089                    crate::codegen::internal_failure("undefined intrinsic is not declared")
1090                })?;
1091                ValType::Ref(RefType {
1092                    nullable: false,
1093                    heap_type: HeapType::Concrete(intrinsic.undefined),
1094                })
1095            }
1096            Type::Error => {
1097                // `Error` only follows a reported typecheck failure.
1098                return Err(crate::codegen::internal_failure(
1099                    "error cannot occupy a value slot",
1100                ));
1101            }
1102            Type::Never => {
1103                // The surrounding expression always diverges, but a `: never` function's
1104                // result slot still needs a concrete Wasm type for the validator. Share
1105                // the `Unknown` lowering — the call always throws before any value reaches
1106                // the slot.
1107                let object_idx = self
1108                    .intrinsic_type_indices()
1109                    .ok_or_else(|| {
1110                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1111                    })?
1112                    .object;
1113                ValType::Ref(RefType {
1114                    nullable: true,
1115                    heap_type: HeapType::Concrete(object_idx),
1116                })
1117            }
1118            Type::Union(members) => {
1119                let lowered: Vec<ValType> = members
1120                    .iter()
1121                    .map(|m| self.value_type(m))
1122                    .collect::<Result<_, _>>()?;
1123                let first_lowering = *lowered.first().ok_or_else(|| {
1124                    crate::codegen::internal_failure("Type::Union holds ≥2 members")
1125                })?;
1126                if lowered.iter().all(|&lowering| lowering == first_lowering) {
1127                    return Ok(first_lowering);
1128                }
1129                // Object-only union → `$ObjectShape`; mixed union → `$Object`.
1130                let all_object_or_null = members
1131                    .iter()
1132                    .all(|m| matches!(m.peel(), Type::Object { .. } | Type::Null));
1133                let heap_idx = if all_object_or_null {
1134                    self.intrinsic_type_indices()
1135                        .ok_or_else(|| {
1136                            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1137                        })?
1138                        .object_shape
1139                } else {
1140                    self.intrinsic_type_indices()
1141                        .ok_or_else(|| {
1142                            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1143                        })?
1144                        .object
1145                };
1146                // Nullable iff some member *lowers* nullable, not iff one is
1147                // spelled `null`: a `TypeVar`, `GenericParam`, `unknown`, or an
1148                // unrecorded `ClassRef` can hold null after instantiation
1149                // without naming it, and a non-null slot would trap the write.
1150                let nullable = lowered.iter().copied().any(is_nullable_ref)
1151                    || members
1152                        .iter()
1153                        .any(|member| matches!(member.peel(), Type::Undefined));
1154                ValType::Ref(RefType {
1155                    nullable,
1156                    heap_type: HeapType::Concrete(heap_idx),
1157                })
1158            }
1159            // ClassRef lowers to the universal `(ref null $Object)` in this
1160            // typecheck-only slice; the concrete `(ref $Foo)` lands with class
1161            // codegen (SUB-480).
1162            // A locally-known class lowers to its concrete `(ref $Foo)` (non-null);
1163            // `struct.new`/`struct.get`/`struct.set` need the concrete type. A class
1164            // not in this module's rec group (cross-package, SUB-488) falls back to
1165            // the universal `(ref null $Object)`.
1166            Type::ClassRef { mangled, .. } => match self.class_struct_type_idx(mangled) {
1167                Some(idx) => ValType::Ref(RefType {
1168                    nullable: false,
1169                    heap_type: HeapType::Concrete(idx),
1170                }),
1171                None => ValType::Ref(RefType {
1172                    nullable: true,
1173                    heap_type: HeapType::Concrete(
1174                        self.intrinsic_type_indices()
1175                            .ok_or_else(|| {
1176                                crate::codegen::internal_failure(
1177                                    "intrinsics declared by codegen entry",
1178                                )
1179                            })?
1180                            .object,
1181                    ),
1182                }),
1183            },
1184            Type::InterfaceRef { .. } => {
1185                let object_idx = self
1186                    .intrinsic_type_indices()
1187                    .ok_or_else(|| {
1188                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1189                    })?
1190                    .object;
1191                ValType::Ref(RefType {
1192                    nullable: true,
1193                    heap_type: HeapType::Concrete(object_idx),
1194                })
1195            }
1196            Type::AliasRef { .. } => {
1197                // A recursion back-edge's runtime value is a heap object
1198                // (object-shaped alias) or a boxed/erased union member —
1199                // both lower to the universal `(ref null $Object)`, the
1200                // same as `InterfaceRef`. The structural type is tracked
1201                // only at the typechecker layer.
1202                let object_idx = self
1203                    .intrinsic_type_indices()
1204                    .ok_or_else(|| {
1205                        crate::codegen::internal_failure("intrinsics declared by codegen entry")
1206                    })?
1207                    .object;
1208                ValType::Ref(RefType {
1209                    nullable: true,
1210                    heap_type: HeapType::Concrete(object_idx),
1211                })
1212            }
1213            Type::NumberEnum { .. } => {
1214                let idx = self.boxed_number_type_idx().ok_or_else(|| {
1215                    crate::codegen::internal_failure(
1216                        "Type::NumberEnum requires the intrinsic types to be declared",
1217                    )
1218                })?;
1219                ValType::Ref(RefType {
1220                    nullable: false,
1221                    heap_type: HeapType::Concrete(idx),
1222                })
1223            }
1224            Type::StringEnum { .. } => {
1225                let idx = self.string_type_idx().ok_or_else(|| {
1226                    crate::codegen::internal_failure(
1227                        "Type::StringEnum requires the intrinsic types to be declared",
1228                    )
1229                })?;
1230                ValType::Ref(RefType {
1231                    nullable: false,
1232                    heap_type: HeapType::Concrete(idx),
1233                })
1234            }
1235            Type::Alias { .. } | Type::Refined { .. } | Type::Readonly(_) => {
1236                return Err(crate::codegen::internal_failure(
1237                    "unpeeled alias in value type lowering",
1238                ));
1239            }
1240        })
1241    }
1242
1243    pub fn wasm_result(
1244        &self,
1245        ty: &Type,
1246    ) -> Result<Vec<ValType>, crate::compiler_error::CompilerFailure> {
1247        Ok(vec![self.value_type(ty)?])
1248    }
1249
1250    /// Wasm value type of a class-method **vtable-slot** parameter or result,
1251    /// of a **constructor** parameter, or of a **box cell**'s payload.
1252    ///
1253    /// These slots erase every class type and type variable to the boxed
1254    /// object slot. [`value_type`](Self::value_type) cannot serve here because
1255    /// its `ClassRef` lowering is phase-dependent: it yields the concrete
1256    /// struct once that struct type is recorded, and imported classes are
1257    /// reconstructed *before* local slot signatures are built while local
1258    /// classes are reserved *after* — so one declaration would lower two ways.
1259    /// Erasing unconditionally keeps a slot's ABI stable across phases and
1260    /// identical between a producer and a cross-package consumer, and keeps it
1261    /// in step with the types recorded for each slot ([`MethodSlotAbi`] for a
1262    /// method, `class_ctor_abi` for a constructor).
1263    pub fn slot_value_type(
1264        &self,
1265        ty: &Type,
1266    ) -> Result<ValType, crate::compiler_error::CompilerFailure> {
1267        if is_erased(ty) {
1268            let object = self
1269                .intrinsic_type_indices()
1270                .ok_or_else(|| {
1271                    crate::codegen::internal_failure(
1272                        "intrinsics declared before class slot signatures",
1273                    )
1274                })?
1275                .object;
1276            return Ok(ValType::Ref(RefType {
1277                nullable: true,
1278                heap_type: HeapType::Concrete(object),
1279            }));
1280        }
1281        self.value_type(ty)
1282    }
1283
1284    pub fn slot_wasm_result(
1285        &self,
1286        ty: &Type,
1287    ) -> Result<Vec<ValType>, crate::compiler_error::CompilerFailure> {
1288        Ok(vec![self.slot_value_type(ty)?])
1289    }
1290
1291    pub fn host_value_type(
1292        &self,
1293        ty: &Type,
1294    ) -> Result<ValType, crate::compiler_error::CompilerFailure> {
1295        if matches!(ty, Type::String | Type::StringLiteral(_)) {
1296            let idx = self.raw_string_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::String requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1297            return Ok(ValType::Ref(RefType {
1298                nullable: false,
1299                heap_type: HeapType::Concrete(idx),
1300            }));
1301        }
1302        if matches!(ty, Type::Uint8Array) {
1303            let idx = self.raw_uint8_array_type_idx().ok_or_else(|| crate::codegen::internal_failure("Type::Uint8Array requires the intrinsic types to be declared (declare_intrinsic_types)"))?;
1304            return Ok(ValType::Ref(RefType {
1305                nullable: false,
1306                heap_type: HeapType::Concrete(idx),
1307            }));
1308        }
1309        self.value_type(ty)
1310    }
1311}
1312
1313/// Names of a validator key's type arguments when every one is a generic
1314/// parameter, so one validator serves each instantiation.
1315fn generic_parameter_names(args: &[Type]) -> Option<Vec<String>> {
1316    if args.is_empty() {
1317        return None;
1318    }
1319    args.iter()
1320        .map(|arg| match arg {
1321            Type::TypeVar(name) => Some(name.clone()),
1322            _ => None,
1323        })
1324        .collect()
1325}
1326
1327#[cfg(test)]
1328mod tests {
1329    use super::*;
1330
1331    #[test]
1332    fn func_idx_round_trip_across_name_shapes() {
1333        let mut map = SymbolTable::default();
1334        map.record_local_fn(
1335            crate::mangle::package_symbol("main", "main"),
1336            10,
1337            Vec::new(),
1338            Type::Void,
1339        )
1340        .unwrap();
1341        let unmangled: MangledName = serde_json::from_str("\"main\"").unwrap();
1342        assert!(
1343            map.record_local_fn(unmangled.clone(), 13, Vec::new(), Type::Void)
1344                .is_err()
1345        );
1346        assert_eq!(map.func_idx(&unmangled), None);
1347        map.record_imported_fn(
1348            crate::mangle::prelude("string_concat"),
1349            11,
1350            vec![Type::String, Type::String],
1351            Type::String,
1352            false,
1353        );
1354        map.record_func(
1355            crate::mangle::extend(&crate::mangle::prelude("Number"), "toString"),
1356            12,
1357        );
1358
1359        assert_eq!(
1360            map.func_idx(&crate::mangle::package_symbol("main", "main")),
1361            Some(10)
1362        );
1363        assert_eq!(map.prelude_func_idx("string_concat"), Some(11));
1364        assert_eq!(
1365            map.func_idx(&crate::mangle::extend(
1366                &crate::mangle::prelude("Number"),
1367                "toString",
1368            )),
1369            Some(12),
1370        );
1371
1372        assert_eq!(
1373            map.func_idx(&crate::mangle::package_symbol("main", "string_concat")),
1374            None,
1375        );
1376        assert_eq!(map.prelude_func_idx("toString"), None);
1377    }
1378}