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