Skip to main content

submilli_engine/codegen/
classes.rs

1//! WasmGC type emission and body lowering for user `class` declarations.
2//!
3//! Each class becomes one rec group holding a struct type
4//! `$Foo (sub $ObjectShape …)` and a vtable type `$Foo_vtable (sub $VTable …)`,
5//! with subclasses subtyping their parent's pair. Alongside those: the
6//! per-class singleton globals (vtable instance, field-names array), the
7//! allocating constructor and its self-first init counterpart, real method
8//! bodies, one closure-ABI adapter per non-generic vtable *slot* for
9//! interface-typed dispatch (inherited slots included — a subclass reuses its
10//! ancestor's adapter where one exists locally),
11//! and the four universal vtable bodies (`toString`/`toJson`/`equals`/`hash`).
12//!
13//! Data fields live in the shared object-fields payload rather than as named
14//! struct fields, so a class's struct only ever references its own vtable and
15//! the intrinsics — which is what lets a cross-package consumer rebuild just
16//! the classes it uses (see [`super::imported_classes`]).
17
18use std::collections::{BTreeMap, BTreeSet};
19
20use wasm_encoder::{
21    BlockType, CodeSection, CompositeInnerType, CompositeType, ConstExpr, FieldType, Function,
22    FunctionSection, GlobalSection, GlobalType, HeapType, Instruction, RefType, StorageType,
23    StructType, SubType, TypeSection, ValType,
24};
25
26use crate::codegen::CodegenCtx;
27use crate::codegen::closures::ClosureSig;
28use crate::codegen::function_emitter::FunctionEmitter;
29use crate::codegen::imported_classes::ImportedClassLayout;
30use crate::codegen::intrinsics::IntrinsicTypeIndices;
31use crate::codegen::symbol_table::{FieldSetup, MethodSlotAbi, SymbolTable};
32use crate::codegen::{GuardedBodies, internal_failure, next_index, parameter_local, wasm_u32};
33use crate::compiler_error::CompilerFailure;
34use crate::{MangledName, TypedAst, TypedClassDecl, TypedTypeDecl};
35
36/// The four universal vtable slots (toString/toJson/equals/hash) every vtable
37/// carries, each backed by a shared stub function.
38pub(crate) const UNIVERSAL_STUB_COUNT: usize = 4;
39/// `$ClassVTable` field index of the nominal-identity parent link.
40pub(crate) const VTABLE_PARENT_SLOT: u32 = UNIVERSAL_STUB_COUNT as u32;
41/// Whether JSON uses the runtime's default property serializer.
42pub(crate) const VTABLE_DEFAULT_JSON_SLOT: u32 = VTABLE_PARENT_SLOT + 1;
43/// Universal funcrefs, parent link and default-serializer marker precede methods.
44pub(crate) const VTABLE_METHOD_SLOT_BASE: u32 = VTABLE_DEFAULT_JSON_SLOT + 1;
45
46/// One method slot in a class vtable. `name` is the method name; `owner` is the
47/// most-derived class that supplies the body (the class itself for an override,
48/// else the nearest ancestor); `sig_idx` is the Wasm fn-type the slot holds
49/// (allocated by the base declarer and reused by overrides).
50struct MethodSlot {
51    name: String,
52    owner: MangledName,
53    sig_idx: u32,
54    /// Declared parameter/return types of the body backing this slot (the
55    /// *owner's*). Needed to pick the closure signature for the slot's payload
56    /// adapter; its body uses the recorded ABI for physical representations.
57    param_tys: Vec<crate::Type>,
58    argument_metadata: Option<String>,
59    ret_ty: crate::Type,
60    /// A generic method has no adapter and never enters the instance payload.
61    generic: bool,
62}
63
64struct ClassLayout {
65    private_members: BTreeSet<String>,
66    generics: Vec<String>,
67    mangled: MangledName,
68    name: String,
69    /// The class name's span, the declaration a constructor frame points at.
70    name_span: crate::Span,
71    parent: Option<MangledName>,
72    /// Full field list (inherited prefix then own), in object payload order.
73    fields: Vec<FieldLayout>,
74    /// Full method slot list (inherited prefix then own/new), in vtable order.
75    methods: Vec<MethodSlot>,
76    /// Methods declared on *this* class (own or override) — each gets a real body.
77    own_methods: Vec<OwnMethod>,
78    /// Indices into `methods` whose payload adapter *this* class emits. A slot
79    /// inherited from a local ancestor reuses that ancestor's adapter (topo
80    /// order guarantees it exists); an imported owner has no adapter in this
81    /// module, so one is emitted here.
82    adapter_slots: Vec<usize>,
83    /// `this`-class's own constructor params + body. `None` body = no declared
84    /// constructor (synthesize a default that just allocates + returns).
85    ctor_params: Vec<crate::TypedParam>,
86    ctor_body: Option<crate::StmtId>,
87    /// Parameter-property copies then own-field initializers, in execution order.
88    /// Emitted at the post-`super()` field-setup point — after the parent is
89    /// initialized, before the rest of the constructor body.
90    field_setup: Vec<FieldSetup>,
91    /// Accessor property names (own). These are not data fields, so they need a
92    /// per-name `$string` global for the runtime property scan even though they
93    /// never appear in any shape's field-names array.
94    accessor_names: Vec<String>,
95    struct_type_idx: u32,
96    vtable_type_idx: u32,
97    ctor_func_idx: u32,
98    ctor_sig_idx: u32,
99    /// Constructor *init* fn: self-first body that initializes fields on an
100    /// already-allocated instance (`super(...)` calls it; the entry constructor
101    /// delegates to it after allocation).
102    ctor_init_func_idx: u32,
103    ctor_init_sig_idx: u32,
104    getter_func_idx: u32,
105    setter_func_idx: u32,
106    /// Per-class `equals` body (vtable universal slot 2): nominal exact-class
107    /// guard + structural compare of the data-field payload slots.
108    equals_func_idx: u32,
109    /// Per-class universal slot 0 body: a user `toString(): string` method
110    /// thunk, else the `"[object Object]"` default.
111    to_string_func_idx: u32,
112    /// Per-class universal slot 1 body: a user `toJson(): string` method
113    /// thunk, else data-field JSON in canonical sorted-key order.
114    to_json_func_idx: u32,
115    /// Per-class universal slot 3 body: FNV-1a over the data-field payload
116    /// slots, consistent with the `equals` contract.
117    hash_func_idx: u32,
118    guarded_bodies: GuardedBodies,
119}
120
121impl ClassLayout {
122    /// The non-generic vtable slots that enter the instance payload, in slot
123    /// order — every method in the inheritance chain, not just the declared
124    /// ones. An interface-typed receiver resolves a method by scanning this
125    /// payload, and `implements` conformance may be satisfied by an inherited
126    /// member, so a subclass instance has to carry the whole chain.
127    fn payload_slots(&self) -> impl Iterator<Item = &MethodSlot> {
128        self.methods.iter().filter(|s| !s.generic)
129    }
130
131    /// Instance payload names: the full data fields (inherited prefix then own,
132    /// folded in by [`ClassPlan::collect`]) followed by the payload method
133    /// names. Single source of truth for the payload built in
134    /// [`ClassPlan::emit_ctor_body`] and the field-names global that indexes it.
135    fn payload_field_names(&self) -> Vec<super::field_names::FieldName> {
136        let mut names: Vec<_> = self
137            .fields
138            .iter()
139            .map(|f| super::field_names::FieldName {
140                name: f.name.clone(),
141                optional: f.optional,
142                is_accessor: false,
143                is_private: self.private_members.contains(&f.name),
144            })
145            .collect();
146        names.extend(self.payload_slots().map(|s| super::field_names::FieldName {
147            name: s.name.clone(),
148            optional: false,
149            is_accessor: s.name.starts_with("get ") || s.name.starts_with("set "),
150            is_private: self.private_members.contains(&s.name),
151        }));
152        names
153    }
154}
155
156#[derive(Clone)]
157struct FieldLayout {
158    name: String,
159    optional: bool,
160    /// Inherited along the layout prefix, so a subclass that does not redeclare
161    /// still reads at the narrowed type. See [`crate::FieldNarrowingCheck`].
162    narrowing_check: Option<Box<crate::FieldNarrowingCheck>>,
163}
164
165/// A method declared on the class itself (own or override) — gets a real Wasm body.
166#[derive(Clone)]
167struct OwnMethod {
168    name: String,
169    name_span: crate::Span,
170    /// The backtrace name: `C.m`, or `get C.x` / `set C.x` for an accessor.
171    frame_name: String,
172    params: Vec<crate::TypedParam>,
173    body: crate::StmtId,
174    return_type: crate::Type,
175    /// A method declaring its own type parameters is rejected at bind time; the
176    /// flag only exists so codegen can emit an unreachable stub for its slot.
177    generic: bool,
178}
179
180/// The class-emission plan, computed once and threaded through the codegen phases.
181pub struct ClassPlan {
182    package_name: String,
183    classes: Vec<ClassLayout>,
184    /// Shared `unreachable` stubs for the universal vtable slots. No vtable
185    /// references them anymore (every class emits its own four real bodies);
186    /// they're kept only so function numbering stays stable.
187    universal_stubs: [u32; UNIVERSAL_STUB_COUNT],
188}
189
190impl ClassPlan {
191    pub fn is_empty(&self) -> bool {
192        self.classes.is_empty()
193    }
194
195    /// Phase 0: gather class declarations from the typed AST, topologically
196    /// ordered by `extends` (parents before children), and resolve each class's
197    /// full field + method slot layout across the inheritance chain.
198    pub fn collect(
199        ta: &TypedAst,
200        imported: &BTreeMap<MangledName, ImportedClassLayout>,
201    ) -> Result<Self, crate::compiler_error::CompilerFailure> {
202        let by_mangled: BTreeMap<&MangledName, &TypedClassDecl> = ta
203            .types
204            .iter()
205            .filter_map(|d| match d {
206                TypedTypeDecl::Class(c) => Some((&c.mangled_name, c)),
207                _ => None,
208            })
209            .collect();
210
211        let order = topo_order(&by_mangled)?;
212        let mut private_members: BTreeMap<MangledName, BTreeSet<String>> = imported
213            .iter()
214            .map(|(name, layout)| (name.clone(), layout.private_members.clone()))
215            .collect();
216
217        // Build layouts in topo order so a parent's resolved layout is available
218        // when its child is processed.
219        let mut layouts: BTreeMap<MangledName, ResolvedLayout> = BTreeMap::new();
220        let mut ordered: Vec<MangledName> = Vec::new();
221        // Which of each class's data-field slots came from its parent prefix —
222        // read back below to decide which own fields need a construction-time
223        // reset (see [`field_setup_steps`]).
224        let mut inherited_fields: BTreeMap<MangledName, BTreeSet<String>> = BTreeMap::new();
225
226        for mangled in &order {
227            let decl = class_decl(&by_mangled, mangled)?;
228            let mut private = match &decl.extends {
229                Some(parent) => private_members
230                    .get(parent)
231                    .cloned()
232                    .ok_or_else(|| unavailable_parent(parent))?,
233                None => BTreeSet::new(),
234            };
235            for field in &decl.fields {
236                if field.visibility == crate::Visibility::Private {
237                    private.insert(field.name.name.clone());
238                }
239            }
240            for accessor in &decl.accessors {
241                if accessor.visibility() == crate::Visibility::Private {
242                    private.insert(accessor_getter_name(&accessor.name().name));
243                    private.insert(accessor_setter_name(&accessor.name().name));
244                }
245            }
246            private_members.insert(mangled.clone(), private);
247            // Parent prefix: a local parent (resolved earlier this pass) or an
248            // imported parent (its full layout reconstructed in `imported_classes`).
249            let (parent_fields, parent_methods) = match &decl.extends {
250                Some(parent) => parent_prefix(parent, &layouts, imported)?,
251                None => (Vec::new(), Vec::new()),
252            };
253
254            // Slot order is an internal ABI, not source order: own members sort
255            // by name and append after the inherited prefix. A cross-package
256            // consumer derives the identical order from the (sorted) public
257            // `PackageDeclaration` maps, so no order metadata crosses the boundary.
258            // Serialization sorts the full set itself (spec.md §JSON), so payload
259            // order stays free.
260            let mut own_fields: Vec<&crate::TypedClassField> = decl.fields.iter().collect();
261            own_fields.sort_by(|a, b| a.name.name.cmp(&b.name.name));
262            // Accessors lower to internal getter/setter methods here in codegen —
263            // they are not methods in the typed AST or `PackageDeclaration`.
264            let synth_methods = accessor_methods(decl);
265            let mut sorted_methods: Vec<(&crate::TypedClassMethod, String)> = decl
266                .methods
267                .iter()
268                .map(|m| (m, format!("{}.{}", decl.name.name, m.name.name)))
269                .chain(synth_methods.iter().map(|(m, frame)| (m, frame.clone())))
270                .collect();
271            sorted_methods.sort_by(|(a, _), (b, _)| a.name.name.cmp(&b.name.name));
272
273            inherited_fields.insert(
274                mangled.clone(),
275                parent_fields.iter().map(|f| f.name.clone()).collect(),
276            );
277            let mut fields: Vec<FieldLayout> = parent_fields;
278            for f in &own_fields {
279                // A redeclaration reuses the parent's slot. Its guard replaces the
280                // inherited one only if it has its own: redeclaring at the *same*
281                // type as the parent narrows nothing and records none, and the
282                // guard the ancestor installed still describes this slot.
283                if let Some(slot) = fields.iter_mut().find(|e| e.name == f.name.name) {
284                    slot.optional = f.optional;
285                    if let Some(check) = f.narrowing_check.clone() {
286                        slot.narrowing_check = Some(check);
287                    }
288                    continue;
289                }
290                fields.push(FieldLayout {
291                    name: f.name.name.clone(),
292                    optional: f.optional,
293                    narrowing_check: f.narrowing_check.clone(),
294                });
295            }
296
297            let mut methods: Vec<SlotDraft> = parent_methods;
298            for (m, _) in &sorted_methods {
299                let param_tys: Vec<crate::Type> = m.params.iter().map(|p| p.ty.clone()).collect();
300                let argument_metadata = super::call_arguments::typed_metadata(&m.params);
301                if let Some(slot) = methods.iter_mut().find(|s| s.name == m.name.name) {
302                    // Override: same slot, body now supplied by this class — so
303                    // the slot must describe *this* body, not the ancestor's.
304                    slot.owner = decl.mangled_name.clone();
305                    slot.param_tys = param_tys;
306                    slot.argument_metadata = argument_metadata;
307                    slot.ret_ty = m.return_type.clone();
308                    slot.generic = !m.generics.is_empty();
309                } else {
310                    methods.push(SlotDraft {
311                        name: m.name.name.clone(),
312                        owner: decl.mangled_name.clone(),
313                        param_tys,
314                        argument_metadata,
315                        ret_ty: m.return_type.clone(),
316                        generic: !m.generics.is_empty(),
317                    });
318                }
319            }
320            let own_methods: Vec<OwnMethod> = sorted_methods
321                .into_iter()
322                .map(|(m, frame_name)| OwnMethod {
323                    name: m.name.name.clone(),
324                    name_span: m.name.span,
325                    frame_name,
326                    params: m.params.clone(),
327                    body: m.body,
328                    return_type: m.return_type.clone(),
329                    generic: !m.generics.is_empty(),
330                })
331                .collect();
332            layouts.insert(mangled.clone(), (fields, methods, own_methods));
333            ordered.push(mangled.clone());
334        }
335
336        // Effective ctor params per class (topo order, parents first), so an
337        // implicit constructor (no body, has a parent) inherits the nearest
338        // ancestor's params — what the entry constructor takes and forwards.
339        // Seeded with imported parents so a local subclass of one inherits its
340        // params across the package boundary.
341        let mut ctor_params_by_mangled: BTreeMap<MangledName, Vec<crate::TypedParam>> = imported
342            .iter()
343            .map(|(m, l)| (m.clone(), l.ctor_params.clone()))
344            .collect();
345        let mut classes: Vec<ClassLayout> = Vec::with_capacity(ordered.len());
346        for mangled in ordered {
347            let decl = class_decl(&by_mangled, &mangled)?;
348            let (fields, method_drafts, own_methods) = layouts
349                .remove(&mangled)
350                .ok_or_else(|| internal_failure("a class layout was resolved out of order"))?;
351            // An inherited signature comes from the typed AST with the
352            // `extends` clause's type arguments substituted, matching what the
353            // declaration exports and a consumer imports; re-deriving the
354            // parent's unsubstituted parameters here would emit a module that
355            // cannot link.
356            let ctor_params = decl.effective_ctor_params().to_vec();
357            let ctor_body = decl.constructor.as_ref().map(|c| c.body);
358            ctor_params_by_mangled.insert(mangled.clone(), ctor_params.clone());
359            let inherited = inherited_fields.remove(&mangled).ok_or_else(|| {
360                internal_failure("a class's inherited field names were not recorded")
361            })?;
362            let field_setup = field_setup_steps(decl, &inherited)?;
363            // get + set share a property name; dedup for the per-name string globals.
364            let accessor_names: Vec<String> = decl
365                .accessors
366                .iter()
367                .map(|a| a.name().name.clone())
368                .collect::<std::collections::BTreeSet<_>>()
369                .into_iter()
370                .collect();
371            let private_members = private_members
372                .remove(&mangled)
373                .ok_or_else(|| internal_failure("a class's private members were not recorded"))?;
374            classes.push(ClassLayout {
375                private_members,
376                generics: ta
377                    .runtime_class_parameters
378                    .get(&mangled)
379                    .cloned()
380                    .unwrap_or_default(),
381                mangled: mangled.clone(),
382                name: decl.name.name.clone(),
383                name_span: decl.name.span,
384                parent: decl.extends.clone(),
385                fields,
386                field_setup,
387                accessor_names,
388                methods: method_drafts
389                    .into_iter()
390                    .map(|d| MethodSlot {
391                        name: d.name,
392                        owner: d.owner,
393                        sig_idx: 0, // filled in reserve_types
394                        param_tys: d.param_tys,
395                        argument_metadata: d.argument_metadata,
396                        ret_ty: d.ret_ty,
397                        generic: d.generic,
398                    })
399                    .collect(),
400                own_methods,
401                adapter_slots: Vec::new(),
402                ctor_params,
403                ctor_body,
404                struct_type_idx: 0,
405                vtable_type_idx: 0,
406                ctor_func_idx: 0,
407                ctor_sig_idx: 0,
408                ctor_init_func_idx: 0,
409                ctor_init_sig_idx: 0,
410                getter_func_idx: 0,
411                setter_func_idx: 0,
412                equals_func_idx: 0,
413                to_string_func_idx: 0,
414                to_json_func_idx: 0,
415                hash_func_idx: 0,
416                guarded_bodies: GuardedBodies::default(),
417            });
418        }
419
420        Ok(ClassPlan {
421            package_name: ta.package_name.to_string(),
422            classes,
423            universal_stubs: [0; UNIVERSAL_STUB_COUNT],
424        })
425    }
426
427    /// The class's instance `Type::ClassRef`, for constructor return types.
428    fn class_ref_ty(&self, class: &ClassLayout) -> crate::Type {
429        crate::Type::class_ref(
430            crate::Package(self.package_name.clone()),
431            class.name.clone(),
432            class.mangled.clone(),
433            Vec::new(),
434        )
435    }
436
437    /// The ordered list of class field-name vectors, for the shared
438    /// field-names-array global emission (`field_names::emit`). Built from the
439    /// same helper the instance payload is, so the two cannot drift.
440    pub fn field_name_lists(&self) -> Vec<Vec<super::field_names::FieldName>> {
441        self.classes
442            .iter()
443            .map(ClassLayout::payload_field_names)
444            .collect()
445    }
446
447    /// Accessor property names across all classes. They need per-name `$string`
448    /// globals for the runtime property scan, but are not data fields, so they're
449    /// excluded from `field_name_lists` (no shape stores them).
450    pub fn accessor_property_names(&self) -> Vec<String> {
451        self.classes
452            .iter()
453            .flat_map(|c| c.accessor_names.iter().cloned())
454            .collect()
455    }
456
457    /// Phase: emit standalone method fn-type sigs, then reserve struct/vtable type
458    /// indices, then emit the class rec group. `types`/`next_type_idx` advance in
459    /// lockstep with the wasm-encoder type section.
460    pub fn reserve_and_emit_types(
461        &mut self,
462        types: &mut TypeSection,
463        next_type_idx: &mut u32,
464        symbols: &mut SymbolTable,
465        intrinsics: IntrinsicTypeIndices,
466    ) -> Result<(), crate::compiler_error::CompilerFailure> {
467        if self.classes.is_empty() {
468            return Ok(());
469        }
470        // 1. A standalone fn-type sig per method slot first introduced (base
471        //    method or subclass-new method); overrides reuse the parent's sig.
472        //    Each sig carries its erasure shape (derived from the originating
473        //    declaration's types) — inherited/override slots copy it with the
474        //    sig, so a call through any class in the chain sees the physical
475        //    slot's boxing needs.
476        let mut sig_by_name: BTreeMap<(MangledName, String), (u32, MethodSlotAbi)> =
477            BTreeMap::new();
478        for class in &self.classes {
479            for slot in &class.methods {
480                // Only allocate when this class is where the slot's sig originates:
481                // i.e. the method is declared here AND the parent has no such slot.
482                let parent_has = match &class.parent {
483                    Some(p) => inherited_slot_sig(&sig_by_name, symbols, p, &slot.name)?,
484                    None => None,
485                };
486                if let Some(parent_entry) = parent_has {
487                    sig_by_name.insert((class.mangled.clone(), slot.name.clone()), parent_entry);
488                    continue;
489                }
490                // Slot originates here: build its sig from the declaring method.
491                // `own_methods` includes the synthetic accessor getter/setter.
492                let method = class
493                    .own_methods
494                    .iter()
495                    .find(|m| m.name == slot.name)
496                    .ok_or_else(|| {
497                        internal_failure("a class method slot originates in a class without it")
498                    })?;
499                let mut params: Vec<ValType> = vec![ref_to(intrinsics.object)];
500                // Overrides may widen parameters beyond the ancestor's representation.
501                // Every method argument therefore crosses a nullable boxed slot.
502                params.extend(
503                    method
504                        .params
505                        .iter()
506                        .map(|_| symbols.value_type(&crate::Type::Unknown))
507                        .collect::<Result<Vec<_>, _>>()?,
508                );
509                let results = symbols.slot_wasm_result(&method.return_type)?;
510                let abi = MethodSlotAbi {
511                    params: params.iter().skip(1).copied().collect(),
512                    ret: results.first().copied(),
513                };
514                let sig_idx = next_index(next_type_idx)?;
515                types.ty().function(params, results);
516                sig_by_name.insert((class.mangled.clone(), slot.name.clone()), (sig_idx, abi));
517            }
518        }
519        // Stamp each slot's sig_idx (resolving inherited slots to their origin sig).
520        for class in &mut self.classes {
521            for slot in &mut class.methods {
522                slot.sig_idx = sig_by_name
523                    .get(&(class.mangled.clone(), slot.name.clone()))
524                    .ok_or_else(|| internal_failure("a class method slot has no signature"))?
525                    .0;
526            }
527        }
528
529        // 2. Reserve struct + vtable type indices: one (vtable, struct) pair per
530        //    class, interleaved, in topo order (parents precede children).
531        for class in &mut self.classes {
532            class.vtable_type_idx = next_index(next_type_idx)?;
533            class.struct_type_idx = next_index(next_type_idx)?;
534            symbols.record_class_vtable_type(class.mangled.clone(), class.vtable_type_idx);
535            symbols.record_class_struct_type(class.mangled.clone(), class.struct_type_idx);
536            let vtable_super = vtable_supertype_idx(class, intrinsics, symbols)?;
537            let struct_super = struct_supertype_idx(class, intrinsics, symbols)?;
538            symbols.record_struct_supertype(class.vtable_type_idx, vtable_super);
539            symbols.record_struct_supertype(class.struct_type_idx, struct_super);
540        }
541
542        // 3. Emit one rec group per class: `(rec $Foo_vtable $Foo)`. A subclass's
543        //    group references its parent's types from an earlier rec group — valid
544        //    because there are no cross-class type cycles (single inheritance; a
545        //    class's fields live in the object-fields payload, not as named struct
546        //    fields, so the struct only references its own vtable + intrinsics).
547        //    Per-class groups let a cross-package consumer reconstruct just the
548        //    classes it uses, byte-identically.
549        for class in &self.classes {
550            let vtable = self.vtable_subtype(class, intrinsics, symbols)?;
551            let strukt = self.struct_subtype(class, intrinsics, symbols)?;
552            types.ty().rec([vtable, strukt]);
553        }
554
555        // Record each field's payload index inside ObjectShape.fields.
556        for class in &self.classes {
557            symbols
558                .class_type_parameters
559                .insert(class.mangled.clone(), class.generics.clone());
560            let parent_guarded = match &class.parent {
561                Some(parent) => {
562                    symbols
563                        .recorded_class_guard_layout(parent)?
564                        .has_instance_guards
565                }
566                None => false,
567            };
568            symbols.record_class_guard_layout(
569                class.mangled.clone(),
570                class.parent.as_ref(),
571                wasm_u32(class.payload_field_names().len())?,
572                !class.generics.is_empty()
573                    || parent_guarded
574                    || class
575                        .fields
576                        .iter()
577                        .any(|field| field.narrowing_check.is_some()),
578            )?;
579            for (i, f) in class.fields.iter().enumerate() {
580                symbols.record_class_field_slot(
581                    class.mangled.clone(),
582                    f.name.clone(),
583                    wasm_u32(i)?,
584                );
585                if let Some(check) = &f.narrowing_check {
586                    symbols.record_class_field_narrowing_check(
587                        class.mangled.clone(),
588                        f.name.clone(),
589                        (**check).clone(),
590                    );
591                }
592            }
593            // Method vtable slot (universal slots + parent link precede user
594            // methods) + its sig, for every slot in the chain so a parent-typed
595            // receiver resolves too.
596            for (i, slot) in class.methods.iter().enumerate() {
597                if !slot.generic && !slot.name.starts_with("get ") && !slot.name.starts_with("set ")
598                {
599                    symbols.record_class_payload_method(class.mangled.clone(), slot.name.clone());
600                }
601                symbols.record_class_method_slot(
602                    class.mangled.clone(),
603                    slot.name.clone(),
604                    method_vtable_slot(i)?,
605                );
606                symbols.record_class_method_sig(
607                    class.mangled.clone(),
608                    slot.name.clone(),
609                    slot.sig_idx,
610                );
611                if let Some((_, abi)) = sig_by_name.get(&(class.mangled.clone(), slot.name.clone()))
612                {
613                    symbols.record_class_method_abi(
614                        class.mangled.clone(),
615                        slot.name.clone(),
616                        abi.clone(),
617                    );
618                }
619            }
620        }
621
622        // 4. Constructor fn-type sigs — standalone, emitted *after* the rec group
623        //    so they can reference `(ref $Foo)` (a rec-group type) as the return.
624        //    Each class gets two: the allocating entry `(params) -> (ref $Foo)`,
625        //    and the self-first init `((ref $Object), params) -> ()`. Parameters
626        //    erase like method slots so a cross-package consumer reconstructs
627        //    the same signature whatever order it rebuilds the classes in.
628        let object_idx = intrinsics.object;
629        for class in &mut self.classes {
630            let params: Vec<ValType> = class
631                .ctor_params
632                .iter()
633                .map(|p| symbols.slot_value_type(&p.ty))
634                .collect::<Result<_, _>>()?;
635            symbols.record_class_ctor_abi(class.mangled.clone(), params.clone());
636            let ret = ref_to(class.struct_type_idx);
637            let mut entry_params = params.clone();
638            if symbols
639                .recorded_class_guard_layout(&class.mangled)?
640                .has_instance_guards
641            {
642                entry_params.push(ref_to(intrinsics.object_fields));
643            }
644            class.ctor_sig_idx = next_index(next_type_idx)?;
645            types.ty().function(entry_params, vec![ret]);
646
647            let mut init_params = vec![ref_to(object_idx)];
648            init_params.extend(params);
649            class.ctor_init_sig_idx = next_index(next_type_idx)?;
650            types.ty().function(init_params, Vec::new());
651        }
652        Ok(())
653    }
654
655    fn vtable_subtype(
656        &self,
657        class: &ClassLayout,
658        intrinsics: IntrinsicTypeIndices,
659        symbols: &SymbolTable,
660    ) -> Result<SubType, CompilerFailure> {
661        let supertype = vtable_supertype_idx(class, intrinsics, symbols)?;
662        let mut fields: Vec<FieldType> = vec![
663            fieldtype_ref(intrinsics.to_string_fn),
664            fieldtype_ref(intrinsics.to_json_fn),
665            fieldtype_ref(intrinsics.equals_fn),
666            fieldtype_ref(intrinsics.hash_fn),
667            fieldtype_ref_null(intrinsics.class_vtable),
668            FieldType {
669                element_type: StorageType::Val(ValType::I32),
670                mutable: false,
671            },
672        ];
673        for slot in &class.methods {
674            fields.push(fieldtype_ref(slot.sig_idx));
675        }
676        Ok(substruct(fields, Some(supertype)))
677    }
678
679    fn struct_subtype(
680        &self,
681        class: &ClassLayout,
682        intrinsics: IntrinsicTypeIndices,
683        symbols: &SymbolTable,
684    ) -> Result<SubType, CompilerFailure> {
685        let supertype = struct_supertype_idx(class, intrinsics, symbols)?;
686        // Header slots 0-2: vtable (refined to this class's vtable),
687        // field-names, object-fields. Wasm requires the full supertype prefix
688        // to be re-listed.
689        let fields: Vec<FieldType> = vec![
690            fieldtype_ref(class.vtable_type_idx),
691            FieldType {
692                mutable: true,
693                ..fieldtype_ref(intrinsics.field_names)
694            },
695            FieldType {
696                mutable: true,
697                ..fieldtype_ref(intrinsics.object_fields)
698            },
699            FieldType {
700                element_type: StorageType::Val(ValType::Ref(RefType::ANYREF)),
701                mutable: true,
702            },
703            FieldType {
704                element_type: StorageType::Val(ValType::I64),
705                mutable: true,
706            },
707        ];
708        Ok(substruct(fields, Some(supertype)))
709    }
710
711    /// Phase: reserve function indices — 4 shared universal stubs, then per-class
712    /// method stubs (one per own method) + getter + setter.
713    pub fn allocate_funcs(
714        &mut self,
715        next_func_idx: &mut u32,
716        symbols: &mut SymbolTable,
717    ) -> Result<(), CompilerFailure> {
718        if self.classes.is_empty() {
719            return Ok(());
720        }
721        self.universal_stubs = [
722            next_index(next_func_idx)?,
723            next_index(next_func_idx)?,
724            next_index(next_func_idx)?,
725            next_index(next_func_idx)?,
726        ];
727        // Borrow-checker: build the (mangled, ctor params, ret) tuples first so we
728        // can call the `&self` helper `class_ref_ty` before the `&mut` loop.
729        let ctor_infos: Vec<(MangledName, Vec<crate::Type>, crate::Type)> = self
730            .classes
731            .iter()
732            .map(|c| {
733                (
734                    crate::mangle::extend(&c.mangled, "constructor"),
735                    c.ctor_params.iter().map(|p| p.ty.clone()).collect(),
736                    self.class_ref_ty(c),
737                )
738            })
739            .collect();
740        for (class, (ctor_mangled, ctor_param_tys, ret)) in self.classes.iter_mut().zip(ctor_infos)
741        {
742            // Constructor first (its index backs the `new Foo(...)` direct call),
743            // then its init body (backs `super(...)` and the entry's delegation).
744            class.ctor_func_idx = next_index(next_func_idx)?;
745            symbols.record_local_fn(ctor_mangled, class.ctor_func_idx, ctor_param_tys, ret)?;
746            class.ctor_init_func_idx = next_index(next_func_idx)?;
747            symbols.record_class_ctor_init_func(class.mangled.clone(), class.ctor_init_func_idx);
748            symbols.record_class_field_setup(class.mangled.clone(), class.field_setup.clone());
749            for method in &class.own_methods {
750                let idx = next_index(next_func_idx)?;
751                symbols.record_class_method_func(class.mangled.clone(), method.name.clone(), idx);
752            }
753            // One closure-ABI adapter per non-generic *slot* — inherited ones
754            // included, since an interface-typed receiver scans the instance
755            // payload, which carries the whole chain. An override rewrites the
756            // slot's owner to this class, so a declared method still allocates
757            // here; a slot inherited from a local ancestor reuses that
758            // ancestor's adapter, which is valid because the adapter takes its
759            // receiver as `(ref $Object)` and casts.
760            let mut adapter_slots: Vec<usize> = Vec::new();
761            for (i, slot) in class.methods.iter().enumerate() {
762                if slot.generic {
763                    continue;
764                }
765                let reused = (slot.owner != class.mangled)
766                    .then(|| symbols.class_method_adapter_func_idx(&slot.owner, &slot.name))
767                    .flatten();
768                let idx = match reused {
769                    Some(idx) => idx,
770                    None => next_index(next_func_idx)?,
771                };
772                if reused.is_none() {
773                    adapter_slots.push(i);
774                    // Also key it by the owner, so descendants of this class
775                    // find it. Without this an import-owned slot re-emits an
776                    // identical adapter at every level of a local chain: the
777                    // owner is the imported class all the way down, and only
778                    // the emitting class was ever recorded.
779                    symbols.record_class_method_adapter_func(
780                        slot.owner.clone(),
781                        slot.name.clone(),
782                        idx,
783                    );
784                }
785                symbols.record_class_method_adapter_func(
786                    class.mangled.clone(),
787                    slot.name.clone(),
788                    idx,
789                );
790            }
791            class.adapter_slots = adapter_slots;
792            // Retained temporarily for stable function/global numbering while
793            // class field dispatch migrates to the object-fields payload.
794            class.getter_func_idx = next_index(next_func_idx)?;
795            class.setter_func_idx = next_index(next_func_idx)?;
796            class.equals_func_idx = next_index(next_func_idx)?;
797            class.to_string_func_idx = next_index(next_func_idx)?;
798            class.to_json_func_idx = next_index(next_func_idx)?;
799            class.hash_func_idx = next_index(next_func_idx)?;
800            // Field order is allocation order, which the body emission follows.
801            class.guarded_bodies = GuardedBodies {
802                equals: next_index(next_func_idx)?,
803                to_json: next_index(next_func_idx)?,
804                hash: next_index(next_func_idx)?,
805            };
806        }
807        Ok(())
808    }
809
810    /// Phase: function-section entries for every emitted function, in the same
811    /// order as `allocate_funcs` / `emit_bodies`.
812    pub fn emit_function_entries(
813        &self,
814        functions: &mut FunctionSection,
815        symbols: &SymbolTable,
816        intrinsics: IntrinsicTypeIndices,
817    ) -> Result<(), crate::compiler_error::CompilerFailure> {
818        if self.classes.is_empty() {
819            return Ok(());
820        }
821        functions.function(intrinsics.to_string_fn);
822        functions.function(intrinsics.to_json_fn);
823        functions.function(intrinsics.equals_fn);
824        functions.function(intrinsics.hash_fn);
825        for class in &self.classes {
826            functions.function(class.ctor_sig_idx);
827            functions.function(class.ctor_init_sig_idx);
828            for method in &class.own_methods {
829                let sig_idx = class
830                    .methods
831                    .iter()
832                    .find(|s| s.name == method.name)
833                    .ok_or_else(|| internal_failure("a class's own method has no vtable slot"))?
834                    .sig_idx;
835                functions.function(sig_idx);
836            }
837            for &i in &class.adapter_slots {
838                let ty = symbols
839                    .closure_func_type_idx(slot_closure_sig(class.methods.get(i).ok_or_else(
840                        || {
841                            crate::codegen::internal_failure(
842                                "class adapter refers to a missing method slot",
843                            )
844                        },
845                    )?)?)
846                    .ok_or_else(|| {
847                        crate::codegen::internal_failure(
848                            "class method closure sig registered (closures::class_member_sigs)",
849                        )
850                    })?;
851                functions.function(ty);
852            }
853            functions.function(intrinsics.field_getter);
854            functions.function(intrinsics.field_setter);
855            functions.function(intrinsics.equals_fn);
856            functions.function(intrinsics.to_string_fn);
857            functions.function(intrinsics.to_json_fn);
858            functions.function(intrinsics.hash_fn);
859            functions.function(intrinsics.equals_fn);
860            functions.function(intrinsics.to_json_fn);
861            functions.function(intrinsics.hash_fn);
862        }
863        Ok(())
864    }
865
866    /// Phase: vtable instance, getter, and setter globals (the field-names array
867    /// global is emitted separately via `field_names::emit`).
868    pub fn emit_globals(
869        &self,
870        globals: &mut GlobalSection,
871        symbols: &mut SymbolTable,
872        next_global_idx: &mut u32,
873        intrinsics: IntrinsicTypeIndices,
874    ) -> Result<(), CompilerFailure> {
875        for class in &self.classes {
876            // Vtable instance: the four per-class universal bodies + the
877            // parent-vtable link + a ref.func per method slot (resolved to the
878            // owning class's body), then struct.new $Foo_vtable. The parent
879            // link is a global.get of the parent's singleton — an earlier
880            // defined global (topo order, parents first) or an imported one
881            // (cross-package / `Error`); both are valid in a const expr.
882            let parent_link = match &class.parent {
883                Some(p) => {
884                    Instruction::GlobalGet(symbols.class_vtable_global_idx(p).ok_or_else(|| {
885                        internal_failure("a parent vtable global was not recorded before its child")
886                    })?)
887                }
888                None => Instruction::RefNull(HeapType::Concrete(intrinsics.class_vtable)),
889            };
890            let mut instrs: Vec<Instruction<'_>> = vec![
891                Instruction::RefFunc(class.to_string_func_idx),
892                Instruction::RefFunc(class.to_json_func_idx),
893                Instruction::RefFunc(class.equals_func_idx),
894                Instruction::RefFunc(class.hash_func_idx),
895                parent_link,
896                Instruction::I32Const(i32::from(
897                    !class.methods.iter().any(|slot| slot.name == "toJson"),
898                )),
899            ];
900            for slot in &class.methods {
901                let func = symbols
902                    .class_method_func_idx(&slot.owner, &slot.name)
903                    .ok_or_else(|| internal_failure("a class method body was not allocated"))?;
904                instrs.push(Instruction::RefFunc(func));
905            }
906            instrs.push(Instruction::StructNew(class.vtable_type_idx));
907            globals.global(
908                GlobalType {
909                    val_type: ref_to(class.vtable_type_idx),
910                    mutable: false,
911                    shared: false,
912                },
913                &ConstExpr::extended(instrs),
914            );
915            symbols.record_class_vtable_global(class.mangled.clone(), next_index(next_global_idx)?);
916
917            // Getter + setter globals are no longer part of ObjectShape, but keep
918            // emitting them for now so existing numbering remains stable.
919            globals.global(
920                GlobalType {
921                    val_type: ref_to(intrinsics.field_getter),
922                    mutable: false,
923                    shared: false,
924                },
925                &ConstExpr::extended([Instruction::RefFunc(class.getter_func_idx)]),
926            );
927            symbols.record_class_getter_global(class.mangled.clone(), next_index(next_global_idx)?);
928
929            globals.global(
930                GlobalType {
931                    val_type: ref_to(intrinsics.field_setter),
932                    mutable: false,
933                    shared: false,
934                },
935                &ConstExpr::extended([Instruction::RefFunc(class.setter_func_idx)]),
936            );
937            symbols.record_class_setter_global(class.mangled.clone(), next_index(next_global_idx)?);
938        }
939        Ok(())
940    }
941
942    /// Phase: function bodies — constructor + init, method bodies and their
943    /// closure adapters, then the retired getter/setter stubs (kept for stable
944    /// numbering) and the four per-class universal vtable bodies
945    /// (equals/toString/toJson/hash).
946    pub fn emit_bodies(
947        &self,
948        code: &mut CodeSection,
949        debug: &mut super::dwarf::DebugFunctions,
950        ctx: &crate::codegen::CodegenCtx<'_>,
951    ) -> Result<(), crate::compiler_error::CompilerFailure> {
952        if self.classes.is_empty() {
953            return Ok(());
954        }
955        // The slot-2 stub is unreferenced (each class emits its own equals
956        // body) but kept so function numbering stays in lockstep with
957        // `allocate_funcs` / `emit_function_entries`.
958        for _ in 0..UNIVERSAL_STUB_COUNT {
959            code.function(&stub_body());
960        }
961        for class in &self.classes {
962            code.function(&self.emit_ctor_body(class, ctx)?);
963            // The constructor body and field initializers run in the init fn, so
964            // that is the frame a failure in them names.
965            debug.write(
966                code,
967                self.emit_ctor_init(class, ctx)?,
968                format!("new {}", class.name),
969                class.name_span,
970            )?;
971            for method in &class.own_methods {
972                if method.generic {
973                    code.function(&stub_body());
974                    continue;
975                }
976                debug.write(
977                    code,
978                    self.emit_method_body(class, method, ctx)?,
979                    method.frame_name.clone(),
980                    method.name_span,
981                )?;
982            }
983            for &i in &class.adapter_slots {
984                code.function(&self.emit_method_adapter_body(
985                    class,
986                    class.methods.get(i).ok_or_else(|| {
987                        crate::codegen::internal_failure(
988                            "class adapter refers to a missing method slot",
989                        )
990                    })?,
991                    ctx,
992                )?);
993            }
994            // getter, then setter.
995            code.function(&stub_body());
996            code.function(&stub_body());
997            let intrinsics = ctx
998                .symbols
999                .intrinsic_type_indices()
1000                .ok_or_else(|| internal_failure("intrinsic types are not declared"))?;
1001            let bodies = class.guarded_bodies;
1002            code.function(&super::vtable_walk::guarded_body(
1003                bodies.equals,
1004                2,
1005                ValType::I32,
1006                ctx.symbols,
1007            )?);
1008            let string_vtable_global_idx = ctx
1009                .symbols
1010                .prelude_global_idx("string_vtable")
1011                .ok_or_else(|| {
1012                    internal_failure("string_vtable is not imported from the prelude")
1013                })?;
1014            // A user `toString`/`toJson` method fills the universal slot (the
1015            // universal slots require strings even when the authored method's
1016            // physical return has widened to preserve live values).
1017            let user_method_thunk =
1018                |name: &str| -> Result<Option<Function>, crate::compiler_error::CompilerFailure> {
1019                    class
1020                        .methods
1021                        .iter()
1022                        .find(|s| s.name == name)
1023                        .map(|slot| emit_universal_slot_thunk(ctx, &slot.name))
1024                        .transpose()
1025                };
1026            let extends_error = ctx
1027                .symbols
1028                .class_struct_type_idx(&crate::mangle::prelude("Error"))
1029                .is_some_and(|error_type| {
1030                    ctx.symbols.ref_fits_slot(
1031                        RefType {
1032                            nullable: false,
1033                            heap_type: HeapType::Concrete(class.struct_type_idx),
1034                        },
1035                        RefType {
1036                            nullable: false,
1037                            heap_type: HeapType::Concrete(error_type),
1038                        },
1039                    )
1040                });
1041            let to_string = match user_method_thunk("toString")? {
1042                Some(thunk) => thunk,
1043                None if class.fields.iter().any(|field| field.name == "toString") => {
1044                    emit_class_to_string_field_body(ctx.symbols, intrinsics)?
1045                }
1046                None if extends_error => {
1047                    emit_error_slot_body(ctx.symbols, intrinsics, 0, intrinsics.to_string_fn)?
1048                }
1049                None => emit_class_to_string_body(intrinsics, string_vtable_global_idx)?,
1050            };
1051            code.function(&to_string);
1052            code.function(&super::vtable_walk::guarded_body(
1053                bodies.to_json,
1054                1,
1055                ref_to(intrinsics.string),
1056                ctx.symbols,
1057            )?);
1058            code.function(&super::vtable_walk::guarded_body(
1059                bodies.hash,
1060                1,
1061                ValType::I32,
1062                ctx.symbols,
1063            )?);
1064            let field_count = wasm_u32(class.fields.len())?;
1065            if extends_error {
1066                let mut equals = Function::new([]);
1067                equals.instruction(&Instruction::LocalGet(0));
1068                equals.instruction(&Instruction::LocalGet(1));
1069                equals.instruction(&Instruction::RefEq);
1070                equals.instruction(&Instruction::End);
1071                code.function(&equals);
1072            } else {
1073                code.function(&emit_class_equals_body(field_count, intrinsics));
1074            }
1075            let to_json = match user_method_thunk("toJson")? {
1076                Some(thunk) => thunk,
1077                None => emit_class_to_json_body(ctx.symbols)?,
1078            };
1079            code.function(&to_json);
1080            if extends_error {
1081                code.function(&emit_error_slot_body(
1082                    ctx.symbols,
1083                    intrinsics,
1084                    3,
1085                    intrinsics.hash_fn,
1086                )?);
1087            } else {
1088                code.function(&emit_class_hash_body(field_count, intrinsics));
1089            }
1090        }
1091        Ok(())
1092    }
1093
1094    /// Closure-ABI adapter for one vtable slot: forward the boxed args, call the
1095    /// body backing the slot (self-first), and box a primitive slot return. `env` (slot 0) is the receiver — cast it to `(ref $Object)` for
1096    /// the body's self param, which is why one adapter serves every subclass.
1097    /// Mirrors [`function_adapters::emit_bodies`].
1098    ///
1099    /// `class` is used only for the ABI lookup; every other input comes from
1100    /// the slot. That is sound because a slot's `MethodSlotAbi` is
1101    /// chain-invariant — `reserve_types` propagates the declarer's verbatim,
1102    /// and the one place a slot's types are rewritten (an override) rewrites
1103    /// `owner` too, so an overridden slot never shares an adapter. Classes
1104    /// sharing an adapter therefore necessarily share its ABI. Make a slot's
1105    /// ABI class-dependent and that stops holding, with no validation error to
1106    /// catch it — reuse would box against the wrong shape.
1107    fn emit_method_adapter_body(
1108        &self,
1109        class: &ClassLayout,
1110        slot: &MethodSlot,
1111        ctx: &crate::codegen::CodegenCtx<'_>,
1112    ) -> Result<Function, crate::compiler_error::CompilerFailure> {
1113        use crate::codegen::function_emitter::{FunctionEmitter, cast};
1114
1115        slot_closure_sig(slot)?;
1116        let intrinsics = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
1117            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1118        })?;
1119        let object_ref_null = ValType::Ref(RefType {
1120            nullable: true,
1121            heap_type: HeapType::Concrete(intrinsics.object),
1122        });
1123        let any_ref = ValType::Ref(RefType {
1124            nullable: false,
1125            heap_type: HeapType::ANY,
1126        });
1127        let mut wasm_params: Vec<(crate::Ident, ValType)> = vec![(
1128            crate::Ident {
1129                name: "$__env__".to_string(),
1130                span: crate::Span::at(ctx.file),
1131            },
1132            any_ref,
1133        )];
1134        for (i, _) in slot.param_tys.iter().enumerate() {
1135            wasm_params.push((
1136                crate::Ident {
1137                    name: format!("$__arg{i}__"),
1138                    span: crate::Span::at(ctx.file),
1139                },
1140                object_ref_null,
1141            ));
1142        }
1143        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1144        // The owner supplies the body — a local ancestor's, or an imported
1145        // one's, both recorded under the owner's mangled name.
1146        let method_func = ctx
1147            .symbols
1148            .class_method_func_idx(&slot.owner, &slot.name)
1149            .ok_or_else(|| {
1150                crate::codegen::internal_failure("method body func allocated or imported")
1151            })?;
1152
1153        // self = env, cast to the method body's `(ref $Object)` self param.
1154        // The callee's physical signature is the slot's. Arguments are boxed;
1155        // reference results already carry the value the closure ABI wants.
1156        let abi = ctx
1157            .symbols
1158            .class_method_abi(&class.mangled, &slot.name)
1159            .cloned()
1160            .ok_or_else(|| {
1161                crate::codegen::internal_failure("class method slot ABI is not registered")
1162            })?;
1163        if abi.params.len() != slot.param_tys.len() {
1164            return Err(crate::codegen::internal_failure(
1165                "class method slot ABI has the wrong parameter count",
1166            ));
1167        }
1168        emitter.instruction(Instruction::LocalGet(0));
1169        if slot.argument_metadata.is_some() {
1170            super::call_arguments::unwrap(&mut emitter, ctx)?;
1171        }
1172        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1173            intrinsics.object,
1174        )));
1175        for (i, ty) in slot.param_tys.iter().enumerate() {
1176            emitter.instruction(Instruction::LocalGet(parameter_local(i)?));
1177            if abi.params.get(i) != Some(&object_ref_null) {
1178                crate::codegen::cast_check::emit_checked_parameter_cast_on_stack(
1179                    &mut emitter,
1180                    ctx,
1181                    &crate::Type::Unknown,
1182                    ty,
1183                )?;
1184            }
1185        }
1186        emitter.instruction(Instruction::Call(method_func));
1187        match abi.ret {
1188            Some(ValType::F64) => cast::emit_box(&mut emitter, ctx, &crate::Type::Number)?,
1189            Some(ValType::I32) => cast::emit_box(&mut emitter, ctx, &crate::Type::Boolean)?,
1190            // A never-returning override can inherit a void slot. Its closure
1191            // convention has a result, but the inherited call cannot return.
1192            None if matches!(slot.ret_ty.peel(), crate::Type::Never) => {
1193                emitter.instruction(Instruction::Unreachable);
1194            }
1195            _ => {}
1196        }
1197        emitter.build()
1198    }
1199
1200    /// Method body: bind `this` (`local.get 0 ; ref.cast (ref $Foo)` — the vtable
1201    /// ABI's self param is `(ref $Object)`), then run the typed body. Returns the
1202    /// body's line rows with it. A method with its own type parameters is
1203    /// rejected by the typechecker, so [`Self::emit_bodies`] writes an
1204    /// unreachable stub in its place as defence rather than pending work.
1205    ///
1206    /// The function's Wasm signature is the slot's — the originating ancestor's,
1207    /// which an override's own annotations may differ from in either direction:
1208    /// wider, where a generic class's params/returns erase to
1209    /// `(ref null $Object)`, or narrower, where the ancestor spelled a
1210    /// structural type the override reaches through an interface. So each param
1211    /// binds through a shadow local cast to the body's own type, and each
1212    /// `return` coerces back into the slot. Byte-identical when own == origin.
1213    fn emit_method_body(
1214        &self,
1215        class: &ClassLayout,
1216        method: &OwnMethod,
1217        ctx: &crate::codegen::CodegenCtx<'_>,
1218    ) -> Result<(Function, Vec<(u64, crate::Span)>), crate::compiler_error::CompilerFailure> {
1219        use crate::codegen::function_emitter::{
1220            FunctionEmitter, ReturnTarget, emit_body_end, stmt,
1221        };
1222
1223        let object_idx = ctx
1224            .symbols
1225            .intrinsic_type_indices()
1226            .ok_or_else(|| internal_failure("intrinsic types are not declared"))?
1227            .object;
1228        let abi = ctx
1229            .symbols
1230            .class_method_abi(&class.mangled, &method.name)
1231            .cloned()
1232            .ok_or_else(|| internal_failure("class method slot ABI is not registered"))?;
1233        let mut wasm_params: Vec<(crate::Ident, ValType)> = vec![(
1234            crate::Ident {
1235                name: "this".to_string(),
1236                span: crate::Span::at(ctx.file),
1237            },
1238            ref_to(object_idx),
1239        )];
1240        wasm_params.extend(slot_params(ctx, &method.params, &abi.params)?);
1241        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1242        let param_slots = rebind_erased_params(&mut emitter, ctx, &method.params, &abi.params)?;
1243        emitter.prepare_parameter_initialization(Some(method.body))?;
1244        // Param prologue boxes captured-mutated method params.
1245        emitter.emit_boxed_param_prologue(&method.params, &param_slots)?;
1246
1247        // `this` = ref.cast of the self param to the concrete class.
1248        let this_slot = emitter.add_anonymous_local(ref_to(class.struct_type_idx))?;
1249        emitter.instruction(Instruction::LocalGet(0));
1250        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1251            class.struct_type_idx,
1252        )));
1253        emitter.instruction(Instruction::LocalSet(this_slot));
1254        emitter.set_this_local(this_slot)?;
1255        super::field_guards::bind_receiver(&mut emitter, ctx, this_slot, &class.mangled)?;
1256
1257        {
1258            emitter.set_return_target(ReturnTarget::Slot(match abi.ret {
1259                Some(ty) => ty,
1260                None => ctx.symbols.slot_value_type(&method.return_type)?,
1261            }));
1262        }
1263        stmt::emit_statement(&mut emitter, ctx, method.body)?;
1264        emit_body_end(&mut emitter, ctx, &method.return_type)?;
1265        emitter.build_with_lines()
1266    }
1267
1268    /// Entry constructor (`new Foo(...)`): `struct.new $Foo` (header globals +
1269    /// default field values + method closures) into a `this` local, delegate
1270    /// field initialization to the self-first init fn, then return the instance.
1271    /// Identical for base and derived classes — the struct is allocated once at
1272    /// the most-derived type, and `super(...)` chains run inside the init fns.
1273    fn emit_ctor_body(
1274        &self,
1275        class: &ClassLayout,
1276        ctx: &crate::codegen::CodegenCtx<'_>,
1277    ) -> Result<Function, crate::compiler_error::CompilerFailure> {
1278        use crate::codegen::function_emitter::FunctionEmitter;
1279
1280        // Parameters stay at their (possibly erased) slot types: this body only
1281        // forwards them to the init fn, which takes the same slots.
1282        let ctor_slots = ctor_slot_types(ctx, &class.mangled)?;
1283        let mut wasm_params = slot_params(ctx, &class.ctor_params, &ctor_slots)?;
1284        let guarded = ctx
1285            .symbols
1286            .recorded_class_guard_layout(&class.mangled)?
1287            .has_instance_guards;
1288        if guarded {
1289            let intr = ctx
1290                .symbols
1291                .intrinsic_type_indices()
1292                .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1293            wasm_params.push((
1294                crate::Ident {
1295                    name: "$field_guards".into(),
1296                    span: crate::Span::at(ctx.file),
1297                },
1298                ref_to(intr.object_fields),
1299            ));
1300        }
1301        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1302
1303        let this_slot = emitter.add_anonymous_local(ref_to(class.struct_type_idx))?;
1304        emitter.set_this_local(this_slot)?;
1305
1306        // struct.new $Foo: vtable + field-names + object-fields payload. The
1307        // payload holds the data fields (slots `0..N_fields`, written by the init
1308        // fn) then one closure per non-generic method (slots `N_fields..`,
1309        // populated by the prologue below). The field-names list must match
1310        // `class_field_names` so the dynamic field-name scan resolves both.
1311        let dynamic_methods: Vec<&MethodSlot> = class.payload_slots().collect();
1312        let n_fields = wasm_u32(class.fields.len())?;
1313        let field_names = class.payload_field_names();
1314        let push_global =
1315            |em: &mut FunctionEmitter, idx: u32| em.instruction(Instruction::GlobalGet(idx));
1316        push_global(
1317            &mut emitter,
1318            ctx.symbols
1319                .class_vtable_global_idx(&class.mangled)
1320                .ok_or_else(|| crate::codegen::internal_failure("vtable global"))?,
1321        );
1322        super::field_names::emit_instance_names(&mut emitter, ctx, &field_names, |_| false)?;
1323        let intrinsics = ctx.symbols.intrinsic_type_indices().ok_or_else(|| {
1324            crate::codegen::internal_failure("intrinsics declared by codegen entry")
1325        })?;
1326        // Instance validators live after the named payload, indexed by data
1327        // slot. Keeping them out of field_names preserves object enumeration.
1328        let layout = ctx.symbols.recorded_class_guard_layout(&class.mangled)?;
1329        let named_len = wasm_u32(field_names.len())?;
1330        let guard_slots = if layout.has_instance_guards {
1331            guard_slot_count(layout.inheritance_depth, named_len)?
1332        } else {
1333            0
1334        };
1335        let payload_len = named_len
1336            .checked_add(guard_slots)
1337            .ok_or_else(|| internal_failure("a class instance payload is too large"))?;
1338        for slot in 0..payload_len {
1339            if slot < named_len {
1340                super::function_emitter::expr::emit_undefined(&mut emitter, ctx)?;
1341            } else {
1342                emitter.instruction(Instruction::RefNull(HeapType::Concrete(intrinsics.object)));
1343            }
1344        }
1345        emitter.instruction(Instruction::ArrayNewFixed {
1346            array_type_index: intrinsics.object_fields,
1347            array_size: payload_len,
1348        });
1349        emitter.instruction(Instruction::RefNull(HeapType::ANY));
1350        emitter.instruction(Instruction::I64Const(0));
1351        emitter.instruction(Instruction::StructNew(class.struct_type_idx));
1352        emitter.instruction(Instruction::LocalSet(this_slot));
1353
1354        if layout.has_instance_guards {
1355            emitter.instruction(Instruction::LocalGet(this_slot));
1356            emitter.instruction(Instruction::StructGet {
1357                struct_type_index: class.struct_type_idx,
1358                field_index: 2,
1359            });
1360            emitter.instruction(Instruction::I32Const(named_len.cast_signed()));
1361            emitter.instruction(Instruction::LocalGet(wasm_u32(class.ctor_params.len())?));
1362            emitter.instruction(Instruction::I32Const(0));
1363            emitter.instruction(Instruction::I32Const(guard_slots.cast_signed()));
1364            emitter.instruction(Instruction::ArrayCopy {
1365                array_type_index_dst: intrinsics.object_fields,
1366                array_type_index_src: intrinsics.object_fields,
1367            });
1368        }
1369
1370        // Method-closure prologue: store `$closure_<sig>{ closure_vtable, adapter,
1371        // this }` into each method's payload slot, so an interface-typed receiver
1372        // dispatches it through the shared field-name scan.
1373        if !dynamic_methods.is_empty() {
1374            let closure_vtable = ctx.symbols.closure_vtable_global_idx().ok_or_else(|| {
1375                crate::codegen::internal_failure(
1376                    "closure vtable global emitted when class methods exist",
1377                )
1378            })?;
1379            for (j, method) in dynamic_methods.iter().enumerate() {
1380                let method_slot = n_fields
1381                    .checked_add(wasm_u32(j)?)
1382                    .ok_or_else(|| internal_failure("a class instance payload is too large"))?;
1383                let closure_struct = ctx
1384                    .symbols
1385                    .closure_struct_type_idx(slot_closure_sig(method)?)
1386                    .ok_or_else(|| {
1387                        crate::codegen::internal_failure("class method closure struct registered")
1388                    })?;
1389                let adapter = ctx
1390                    .symbols
1391                    .class_method_adapter_func_idx(&class.mangled, &method.name)
1392                    .ok_or_else(|| {
1393                        crate::codegen::internal_failure(
1394                            "class method adapter allocated or reused from the owner",
1395                        )
1396                    })?;
1397                emitter.instruction(Instruction::LocalGet(this_slot));
1398                emitter.instruction(Instruction::StructGet {
1399                    struct_type_index: class.struct_type_idx,
1400                    field_index: 2,
1401                });
1402                emitter.instruction(Instruction::I32Const(method_slot.cast_signed()));
1403                emitter.instruction(Instruction::GlobalGet(closure_vtable));
1404                emitter.instruction(Instruction::RefFunc(adapter));
1405                emitter.instruction(Instruction::LocalGet(this_slot));
1406                if let Some(metadata) = &method.argument_metadata {
1407                    super::call_arguments::wrap(&mut emitter, ctx, metadata)?;
1408                }
1409                emitter.instruction(Instruction::I64Const(0));
1410                emitter.instruction(Instruction::StructNew(closure_struct));
1411                emitter.instruction(Instruction::ArraySet(intrinsics.object_fields));
1412                if let Some(field_slot) = class.fields.iter().position(|f| f.name == method.name) {
1413                    emit_fill_optional_method(
1414                        &mut emitter,
1415                        (this_slot, class.struct_type_idx),
1416                        intrinsics.object_fields,
1417                        method_slot,
1418                        wasm_u32(field_slot)?,
1419                    );
1420                }
1421            }
1422        }
1423
1424        // Delegate to the self-first init fn: `Foo#ctor_init(this, params...)`.
1425        // It runs the constructor body (or forwards to the parent for an implicit
1426        // constructor), where `super(...)` chains up to the parent init.
1427        emitter.instruction(Instruction::LocalGet(this_slot));
1428        for i in 0..wasm_u32(class.ctor_params.len())? {
1429            emitter.instruction(Instruction::LocalGet(i));
1430        }
1431        emitter.instruction(Instruction::Call(class.ctor_init_func_idx));
1432
1433        // Implicit `return this`. (Explicit `return;` inside a constructor is not
1434        // supported in this slice — the fixtures don't use it.)
1435        emitter.instruction(Instruction::LocalGet(this_slot));
1436        emitter.build()
1437    }
1438
1439    /// Constructor init fn: self-first ABI `((ref $Object), params...) -> ()` that
1440    /// initializes fields on an already-allocated instance. `super(...)` lowers to
1441    /// a direct call of the parent's init fn (see [`expr`] codegen). A class with
1442    /// no declared constructor but a parent (implicit constructor) forwards all
1443    /// params to the parent init. Returns the body's line rows with it.
1444    fn emit_ctor_init(
1445        &self,
1446        class: &ClassLayout,
1447        ctx: &crate::codegen::CodegenCtx<'_>,
1448    ) -> Result<(Function, Vec<(u64, crate::Span)>), crate::compiler_error::CompilerFailure> {
1449        use crate::codegen::function_emitter::{FunctionEmitter, cast, stmt};
1450
1451        let object_idx = ctx
1452            .symbols
1453            .intrinsic_type_indices()
1454            .ok_or_else(|| internal_failure("intrinsic types are not declared"))?
1455            .object;
1456        let mut wasm_params: Vec<(crate::Ident, ValType)> = vec![(
1457            crate::Ident {
1458                name: "this".to_string(),
1459                span: crate::Span::at(ctx.file),
1460            },
1461            ref_to(object_idx),
1462        )];
1463        let ctor_slots = ctor_slot_types(ctx, &class.mangled)?;
1464        wasm_params.extend(slot_params(ctx, &class.ctor_params, &ctor_slots)?);
1465        let mut emitter = FunctionEmitter::new(ctx, &wasm_params)?;
1466        let param_slots = rebind_erased_params(&mut emitter, ctx, &class.ctor_params, &ctor_slots)?;
1467        emitter.prepare_parameter_initialization(class.ctor_body)?;
1468        // Box captured-mutated ctor params.
1469        emitter.emit_boxed_param_prologue(&class.ctor_params, &param_slots)?;
1470
1471        // `this` = ref.cast of the self param to the concrete class.
1472        let this_slot = emitter.add_anonymous_local(ref_to(class.struct_type_idx))?;
1473        emitter.instruction(Instruction::LocalGet(0));
1474        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1475            class.struct_type_idx,
1476        )));
1477        emitter.instruction(Instruction::LocalSet(this_slot));
1478        emitter.set_this_local(this_slot)?;
1479        super::field_guards::bind_receiver(&mut emitter, ctx, this_slot, &class.mangled)?;
1480        emitter.set_ctor_class(class.mangled.clone());
1481
1482        if let Some(body) = class.ctor_body {
1483            if class.parent.is_none() {
1484                super::function_emitter::expr::emit_class_field_setup_phase(
1485                    &mut emitter,
1486                    ctx,
1487                    &class.mangled,
1488                    super::function_emitter::expr::FieldSetupPhase::Initializers,
1489                )?;
1490            }
1491            let defaults = ctx
1492                .ta
1493                .parameter_default_prologues
1494                .get(&body)
1495                .copied()
1496                .unwrap_or(0);
1497            if class.parent.is_none() && defaults > 0 {
1498                let body = ctx.ta.try_stmt(body).map_err(super::arena_failure)?;
1499                let crate::TypedStmtKind::Block(statements) = &body.kind else {
1500                    return Err(internal_failure(
1501                        "constructor default prologue is not a block",
1502                    ));
1503                };
1504                let prologue = statements.get(..defaults).ok_or_else(|| {
1505                    internal_failure("constructor default prologue is incomplete")
1506                })?;
1507                let body = statements.get(defaults..).ok_or_else(|| {
1508                    internal_failure("constructor body is missing after defaults")
1509                })?;
1510                emitter.push_scope();
1511                for &statement in prologue {
1512                    stmt::emit_statement(&mut emitter, ctx, statement)?;
1513                }
1514                super::function_emitter::expr::emit_class_field_setup_phase(
1515                    &mut emitter,
1516                    ctx,
1517                    &class.mangled,
1518                    super::function_emitter::expr::FieldSetupPhase::ParameterProperties,
1519                )?;
1520                for &statement in body {
1521                    stmt::emit_statement(&mut emitter, ctx, statement)?;
1522                }
1523                emitter.pop_scope()?;
1524            } else {
1525                if class.parent.is_none() {
1526                    super::function_emitter::expr::emit_class_field_setup_phase(
1527                        &mut emitter,
1528                        ctx,
1529                        &class.mangled,
1530                        super::function_emitter::expr::FieldSetupPhase::ParameterProperties,
1531                    )?;
1532                }
1533                stmt::emit_statement(&mut emitter, ctx, body)?;
1534            }
1535        } else {
1536            // Implicit constructor: forward all params to the parent init (if
1537            // any), then run this class's field setup.
1538            if let Some(parent) = &class.parent {
1539                let parent_init =
1540                    ctx.symbols
1541                        .class_ctor_init_func_idx(parent)
1542                        .ok_or_else(|| {
1543                            internal_failure("a parent constructor init was not allocated")
1544                        })?;
1545                // This class's parameters carry the `extends` clause's
1546                // substituted types, while the parent's own signature may
1547                // still erase them, so each one is coerced on the way through.
1548                // Forward from the rebound local, whose Wasm type is `p.ty`'s —
1549                // what the coercion takes as its source.
1550                let parent_slots = ctor_slot_types(ctx, parent)?;
1551                emitter.instruction(Instruction::LocalGet(this_slot));
1552                if param_slots.len() != class.ctor_params.len() {
1553                    return Err(internal_failure(
1554                        "constructor parameters and their locals differ in number",
1555                    ));
1556                }
1557                for (i, (p, local)) in class.ctor_params.iter().zip(&param_slots).enumerate() {
1558                    emitter.instruction(Instruction::LocalGet(*local));
1559                    if let Some(slot) = parent_slots.get(i).copied() {
1560                        cast::emit_coerce_to_wasm_slot(&mut emitter, ctx, &p.ty, slot)?;
1561                    }
1562                }
1563                emitter.instruction(Instruction::Call(parent_init));
1564            }
1565            crate::codegen::function_emitter::expr::emit_class_field_setup(
1566                &mut emitter,
1567                ctx,
1568                &class.mangled,
1569            )?;
1570        }
1571        emitter.build_with_lines()
1572    }
1573
1574    /// `(export name, func index)` pairs for every class function a cross-package
1575    /// consumer may import: the entry constructor, the self-first ctor-init, and
1576    /// every method body. Restricted to classes the package actually exports
1577    /// (`is_exported`). Method bodies include private methods — Wasm-level
1578    /// visibility is wider than source so a cross-package subclass can fill its
1579    /// inherited vtable slots (docs/classes.md §2, §6). Generic methods are
1580    /// excluded (still emitted as stubs this slice).
1581    pub fn exported_funcs(
1582        &self,
1583        symbols: &SymbolTable,
1584        is_exported: impl Fn(&MangledName) -> bool,
1585    ) -> Result<Vec<(MangledName, u32)>, CompilerFailure> {
1586        let mut out: Vec<(MangledName, u32)> = Vec::new();
1587        for class in &self.classes {
1588            if !is_exported(&class.mangled) {
1589                continue;
1590            }
1591            out.push((
1592                crate::mangle::extend(&class.mangled, "constructor"),
1593                class.ctor_func_idx,
1594            ));
1595            out.push((
1596                crate::mangle::extend(&class.mangled, "constructor_init"),
1597                class.ctor_init_func_idx,
1598            ));
1599            for method in &class.own_methods {
1600                if method.generic {
1601                    continue;
1602                }
1603                let idx = symbols
1604                    .class_method_func_idx(&class.mangled, &method.name)
1605                    .ok_or_else(|| internal_failure("a class method body was not allocated"))?;
1606                out.push((crate::mangle::extend(&class.mangled, &method.name), idx));
1607            }
1608        }
1609        Ok(out)
1610    }
1611
1612    /// Each exported class's vtable-singleton global, for the export section —
1613    /// the class's nominal identity, imported by consumers for `instanceof`'s
1614    /// ref.eq comparison and as a local subclass's parent link.
1615    pub fn exported_vtable_globals(
1616        &self,
1617        symbols: &SymbolTable,
1618        is_exported: impl Fn(&MangledName) -> bool,
1619    ) -> Result<Vec<(MangledName, u32)>, CompilerFailure> {
1620        self.classes
1621            .iter()
1622            .filter(|class| is_exported(&class.mangled))
1623            .map(|class| {
1624                let global_idx = symbols
1625                    .class_vtable_global_idx(&class.mangled)
1626                    .ok_or_else(|| internal_failure("a class vtable global was not emitted"))?;
1627                Ok((vtable_global_export_name(&class.mangled), global_idx))
1628            })
1629            .collect()
1630    }
1631
1632    /// Function indices that appear in a `ref.func` const-expr and so must be
1633    /// covered by the declarative element section.
1634    pub fn declared_funcs(&self, symbols: &SymbolTable) -> Vec<u32> {
1635        if self.classes.is_empty() {
1636            return Vec::new();
1637        }
1638        let mut out: Vec<u32> = self.universal_stubs.to_vec();
1639        for class in &self.classes {
1640            // Every vtable slot funcref the vtable global stores — own bodies,
1641            // overrides, and inherited slots whose body lives on an ancestor
1642            // (possibly imported from another package).
1643            for slot in &class.methods {
1644                if let Some(idx) = symbols.class_method_func_idx(&slot.owner, &slot.name) {
1645                    out.push(idx);
1646                }
1647            }
1648            // Method-closure adapters are `ref.func`'d in the constructor
1649            // prologue — one per payload slot, inherited ones included. The
1650            // dedup below absorbs the indices shared with an ancestor.
1651            for slot in class.payload_slots() {
1652                if let Some(idx) = symbols.class_method_adapter_func_idx(&class.mangled, &slot.name)
1653                {
1654                    out.push(idx);
1655                }
1656            }
1657            out.push(class.getter_func_idx);
1658            out.push(class.setter_func_idx);
1659            out.push(class.equals_func_idx);
1660            out.push(class.to_string_func_idx);
1661            out.push(class.to_json_func_idx);
1662            out.push(class.hash_func_idx);
1663        }
1664        out.sort_unstable();
1665        out.dedup();
1666        out
1667    }
1668}
1669
1670/// Constructor field-setup steps for a class: parameter-property copies (in
1671/// constructor-param order) followed by own-field initializers (in declaration
1672/// order). Parameter-property fields are the auto-assigned own fields; their copy
1673/// reads the matching constructor param (init-fn local `position + 1`; self is 0).
1674///
1675/// A field that only redeclares an inherited one contributes a
1676/// [`FieldSetup::Reset`] instead — see [`redeclares_uninitialized_slot`].
1677/// `inherited` names the slots the parent prefix already owns.
1678fn field_setup_steps(
1679    decl: &TypedClassDecl,
1680    inherited: &BTreeSet<String>,
1681) -> Result<Vec<FieldSetup>, CompilerFailure> {
1682    let mut steps = Vec::new();
1683    if let Some(ctor) = &decl.constructor {
1684        for field in decl.fields.iter().filter(|f| f.auto_assigned) {
1685            if let Some(param) = ctor.params.iter().find(|p| p.name.name == field.name.name) {
1686                steps.push(FieldSetup::ParamCopy {
1687                    field: field.name.name.clone(),
1688                    param: Box::new(param.clone()),
1689                });
1690            }
1691        }
1692    }
1693    for field in &decl.fields {
1694        if let Some(value) = field.initializer {
1695            steps.push(FieldSetup::Init {
1696                field: field.name.name.clone(),
1697                value,
1698            });
1699        } else if redeclares_uninitialized_slot(field, inherited) {
1700            steps.push(FieldSetup::Reset {
1701                field: field.name.name.clone(),
1702            });
1703        }
1704    }
1705    Ok(steps)
1706}
1707
1708/// Vtable field of the method in class slot `slot`, after the universal slots
1709/// and parent link.
1710pub(crate) fn method_vtable_slot(slot: usize) -> Result<u32, CompilerFailure> {
1711    VTABLE_METHOD_SLOT_BASE
1712        .checked_add(wasm_u32(slot)?)
1713        .ok_or_else(|| internal_failure("a class has too many vtable slots"))
1714}
1715
1716/// Guard payload slots: one guard per named slot plus its receiver marker, for
1717/// each class level from this one to the root.
1718pub(super) fn guard_slot_count(
1719    inheritance_depth: u32,
1720    named_len: u32,
1721) -> Result<u32, CompilerFailure> {
1722    inheritance_depth
1723        .checked_add(1)
1724        .zip(named_len.checked_add(1))
1725        .and_then(|(levels, per_level)| levels.checked_mul(per_level))
1726        .ok_or_else(|| internal_failure("a class guard payload is too large"))
1727}
1728
1729fn class_decl<'a>(
1730    by_mangled: &BTreeMap<&MangledName, &'a TypedClassDecl>,
1731    mangled: &MangledName,
1732) -> Result<&'a TypedClassDecl, CompilerFailure> {
1733    by_mangled
1734        .get(mangled)
1735        .copied()
1736        .ok_or_else(|| internal_failure("class order names an undeclared class"))
1737}
1738
1739/// Whether this own field shares an inherited slot without writing it. Such a
1740/// field would otherwise read back whatever the *parent's* initializer left
1741/// there — a value the redeclaration's (possibly narrower) type need not admit.
1742/// Only the optional case arises: a non-optional field with no initializer must
1743/// be assigned in the constructor (`definite_assignment`), which runs after this
1744/// setup. A bodyless optional method emits nothing, so it keeps what the slot
1745/// holds, such as a subclass method's closure.
1746fn redeclares_uninitialized_slot(
1747    field: &crate::TypedClassField,
1748    inherited: &BTreeSet<String>,
1749) -> bool {
1750    field.optional
1751        && !field.auto_assigned
1752        && !field.method_signature
1753        && inherited.contains(&field.name.name)
1754}
1755
1756/// Export/linker field name of a class's vtable-singleton global. The space in
1757/// the suffix keeps it out of the member namespace (same trick as
1758/// `get foo`/`set foo`), so no user method can collide with it.
1759pub(crate) fn vtable_global_export_name(mangled: &MangledName) -> MangledName {
1760    crate::mangle::extend(mangled, "vtable global")
1761}
1762
1763/// Internal name of the vtable method an accessor property's getter lowers to.
1764/// The space keeps it disjoint from any user identifier, so it never collides
1765/// with a real method; it is also the runtime key under which the getter closure
1766/// is stored in the object payload. Codegen-only — not a mangled symbol name.
1767pub(crate) fn accessor_getter_name(prop: &str) -> String {
1768    format!("get {prop}")
1769}
1770
1771/// Internal name of the vtable method an accessor property's setter lowers to.
1772pub(crate) fn accessor_setter_name(prop: &str) -> String {
1773    format!("set {prop}")
1774}
1775
1776/// The closure shape an accessor's payload slot holds: a getter takes no
1777/// argument and returns the property (never `void`), a setter takes one and
1778/// returns nothing. Producer (`analysis::note_shaped_property_access`, which
1779/// collects the sig) and consumer (`emit_accessor_slot_present`, which looks up
1780/// its struct index) must agree exactly, so they read it from here.
1781pub(crate) fn accessor_closure_sig(kind: crate::AccessorKind) -> ClosureSig {
1782    match kind {
1783        crate::AccessorKind::Get => ClosureSig {
1784            arity: 0,
1785            is_void: false,
1786        },
1787        crate::AccessorKind::Set => ClosureSig {
1788            arity: 1,
1789            is_void: true,
1790        },
1791    }
1792}
1793
1794/// Whether an own data field redeclares one already in the inherited prefix —
1795/// a shadow, so it reuses the parent's slot rather than appending a second one.
1796/// The slot has to stay at the *parent's* index: a child's data region is a
1797/// prefix-compatible extension of its parent's, which is what lets a
1798/// parent-typed receiver read a child instance at all.
1799///
1800/// Both layout builders must agree on this rule, or the same class gets
1801/// different slot numbers on the two sides of a package boundary. This is the
1802/// `imported_classes` reconstruction's copy; [`ClassPlan::collect`] answers the
1803/// same question with `fields.iter_mut().find(…)`, because it also needs the
1804/// slot itself to carry the redeclaration's narrowing guard across.
1805pub(crate) fn shadows_inherited_field<'a>(
1806    inherited: impl IntoIterator<Item = &'a str>,
1807    name: &str,
1808) -> bool {
1809    inherited.into_iter().any(|f| f == name)
1810}
1811
1812/// Internal getter/setter methods synthesized from a class's accessors. These
1813/// reuse the ordinary method machinery (vtable slots, closures, bodies, dynamic
1814/// dispatch) but exist only inside codegen — never in the typed AST or
1815/// `PackageDeclaration`. Their names (`get <p>` / `set <p>`) are the runtime ABI
1816/// key under which the getter/setter closure is stored in the object payload.
1817///
1818/// Each comes with its backtrace name, `get C.x` / `set C.x`. It is built here
1819/// rather than parsed back out of the internal name, which a method with a
1820/// string-literal key such as `"get x"()` can share.
1821fn accessor_methods(decl: &TypedClassDecl) -> Vec<(crate::TypedClassMethod, String)> {
1822    decl.accessors
1823        .iter()
1824        .map(|acc| {
1825            let (kind, name, params, return_type) = match acc {
1826                crate::TypedClassAccessor::Getter { name, ret_ty, .. } => (
1827                    "get",
1828                    accessor_getter_name(&name.name),
1829                    Vec::new(),
1830                    ret_ty.clone(),
1831                ),
1832                crate::TypedClassAccessor::Setter { name, param, .. } => (
1833                    "set",
1834                    accessor_setter_name(&name.name),
1835                    vec![param.clone()],
1836                    crate::Type::Void,
1837                ),
1838            };
1839            let frame_name = format!("{kind} {}.{}", decl.name.name, acc.name().name);
1840            let method = crate::TypedClassMethod {
1841                name: crate::Ident {
1842                    name,
1843                    span: acc.name().span,
1844                },
1845                generics: Vec::new(),
1846                params,
1847                return_type,
1848                body: acc.body(),
1849                visibility: acc.visibility(),
1850                doc: None,
1851            };
1852            (method, frame_name)
1853        })
1854        .collect()
1855}
1856
1857/// A method overriding an inherited optional method (`f?(): T;`, a data
1858/// field) also fills that field, so a read through the ancestor's type sees the
1859/// override. The field gets the same closure as the method's payload slot.
1860fn emit_fill_optional_method(
1861    emitter: &mut crate::codegen::function_emitter::FunctionEmitter<'_>,
1862    (this_slot, struct_type_index): (u32, u32),
1863    object_fields: u32,
1864    method_slot: u32,
1865    field_slot: u32,
1866) {
1867    let payload = |emitter: &mut crate::codegen::function_emitter::FunctionEmitter<'_>| {
1868        emitter.instruction(Instruction::LocalGet(this_slot));
1869        emitter.instruction(Instruction::StructGet {
1870            struct_type_index,
1871            field_index: 2,
1872        });
1873    };
1874    payload(emitter);
1875    emitter.instruction(Instruction::I32Const(field_slot.cast_signed()));
1876    payload(emitter);
1877    emitter.instruction(Instruction::I32Const(method_slot.cast_signed()));
1878    emitter.instruction(Instruction::ArrayGet(object_fields));
1879    emitter.instruction(Instruction::ArraySet(object_fields));
1880}
1881
1882fn slot_closure_sig(
1883    slot: &MethodSlot,
1884) -> Result<crate::codegen::closures::ClosureSig, crate::compiler_error::CompilerFailure> {
1885    let sig = crate::Type::Function {
1886        params: slot.param_tys.clone(),
1887        ret: Box::new(slot.ret_ty.clone()),
1888        predicate: None,
1889        optional: 0,
1890        has_rest: false,
1891    };
1892    crate::codegen::closures::classify(&sig)
1893}
1894
1895#[derive(Clone)]
1896struct SlotDraft {
1897    name: String,
1898    owner: MangledName,
1899    param_tys: Vec<crate::Type>,
1900    argument_metadata: Option<String>,
1901    ret_ty: crate::Type,
1902    generic: bool,
1903}
1904
1905/// A class's resolved (fields, method slots, own-method names) during the
1906/// inheritance-folding pass in [`ClassPlan::collect`].
1907type ResolvedLayout = (Vec<FieldLayout>, Vec<SlotDraft>, Vec<OwnMethod>);
1908
1909/// The inherited field and method-slot prefix of a class whose parent is
1910/// `parent`: a local class laid out earlier in topological order, or an
1911/// imported one reconstructed before local classes.
1912fn parent_prefix(
1913    parent: &MangledName,
1914    layouts: &BTreeMap<MangledName, ResolvedLayout>,
1915    imported: &BTreeMap<MangledName, ImportedClassLayout>,
1916) -> Result<(Vec<FieldLayout>, Vec<SlotDraft>), CompilerFailure> {
1917    if let Some((fields, methods, _)) = layouts.get(parent) {
1918        return Ok((fields.clone(), methods.clone()));
1919    }
1920    let layout = imported
1921        .get(parent)
1922        .ok_or_else(|| unavailable_parent(parent))?;
1923    let fields = layout
1924        .fields
1925        .iter()
1926        .map(|name| FieldLayout {
1927            name: name.clone(),
1928            optional: layout.optional_fields.contains(name),
1929            narrowing_check: layout.narrowing_checks.get(name).cloned().map(Box::new),
1930        })
1931        .collect();
1932    let methods = layout
1933        .methods
1934        .iter()
1935        .map(|s| SlotDraft {
1936            name: s.name.clone(),
1937            owner: s.owner.clone(),
1938            param_tys: s.param_tys.clone(),
1939            argument_metadata: s.argument_metadata.clone(),
1940            ret_ty: s.ret_ty.clone(),
1941            generic: s.generic,
1942        })
1943        .collect();
1944    Ok((fields, methods))
1945}
1946
1947fn unavailable_parent(parent: &MangledName) -> CompilerFailure {
1948    internal_failure(format!(
1949        "parent class `{parent}` was not laid out before its subclass"
1950    ))
1951}
1952
1953/// The signature and ABI a slot inherits from `parent`, if the parent has the
1954/// slot. An imported parent's slots live in the symbol table (recorded by
1955/// `imported_classes`), not in `sig_by_name`.
1956fn inherited_slot_sig(
1957    sig_by_name: &BTreeMap<(MangledName, String), (u32, MethodSlotAbi)>,
1958    symbols: &SymbolTable,
1959    parent: &MangledName,
1960    slot: &str,
1961) -> Result<Option<(u32, MethodSlotAbi)>, CompilerFailure> {
1962    if let Some(entry) = sig_by_name.get(&(parent.clone(), slot.to_owned())) {
1963        return Ok(Some(entry.clone()));
1964    }
1965    let Some(sig) = symbols.class_method_sig(parent, slot) else {
1966        return Ok(None);
1967    };
1968    let abi = symbols
1969        .class_method_abi(parent, slot)
1970        .cloned()
1971        .ok_or_else(|| internal_failure("an inherited method slot has no recorded ABI"))?;
1972    Ok(Some((sig, abi)))
1973}
1974
1975/// Topological order of classes by `extends` (parents before children). Classes
1976/// whose parent is not in this module (cross-package) are treated as roots.
1977/// Chains are walked iteratively; a cycle is an internal failure because
1978/// inference rejects cyclic hierarchies.
1979fn topo_order(
1980    by_mangled: &BTreeMap<&MangledName, &TypedClassDecl>,
1981) -> Result<Vec<MangledName>, CompilerFailure> {
1982    let mut visited: BTreeSet<&MangledName> = BTreeSet::new();
1983    let mut out: Vec<MangledName> = Vec::new();
1984    for &start in by_mangled.keys() {
1985        let mut chain: Vec<&MangledName> = Vec::new();
1986        let mut next = Some(start);
1987        while let Some(mangled) = next {
1988            if visited.contains(mangled) {
1989                break;
1990            }
1991            let Some(decl) = by_mangled.get(mangled) else {
1992                break;
1993            };
1994            if chain.contains(&mangled) {
1995                return Err(internal_failure("class inheritance is cyclic"));
1996            }
1997            chain.push(mangled);
1998            next = decl.extends.as_ref();
1999        }
2000        for mangled in chain.into_iter().rev() {
2001            visited.insert(mangled);
2002            out.push(mangled.clone());
2003        }
2004    }
2005    Ok(out)
2006}
2007
2008/// The Wasm type of a body's parameter `i` — the recorded slot's, not the
2009/// declaration's own: an override shares the signature of the ancestor that
2010/// introduced the slot, and a constructor's signature erases class types so
2011/// producer and cross-package consumer agree.
2012fn slot_param_type(
2013    ctx: &CodegenCtx<'_>,
2014    slot_types: &[ValType],
2015    i: usize,
2016    p: &crate::TypedParam,
2017) -> Result<ValType, crate::compiler_error::CompilerFailure> {
2018    Ok(match slot_types.get(i) {
2019        Some(ty) => *ty,
2020        None => ctx.symbols.slot_value_type(&p.ty)?,
2021    })
2022}
2023
2024/// The constructor parameter slots recorded when the class's ctor fn-types were
2025/// emitted — the same signature the entry ctor, the init fn, and every call site
2026/// must agree on. Local and imported classes both record it before any body is
2027/// emitted.
2028fn ctor_slot_types(
2029    ctx: &CodegenCtx<'_>,
2030    class: &MangledName,
2031) -> Result<Vec<ValType>, CompilerFailure> {
2032    ctx.symbols
2033        .class_ctor_abi(class)
2034        .map(<[ValType]>::to_vec)
2035        .ok_or_else(|| {
2036            internal_failure(format!(
2037                "class `{class}` has no recorded constructor signature"
2038            ))
2039        })
2040}
2041
2042/// Named Wasm parameters for a method or constructor body, at their slot types.
2043fn slot_params(
2044    ctx: &CodegenCtx<'_>,
2045    params: &[crate::TypedParam],
2046    slot_types: &[ValType],
2047) -> Result<Vec<(crate::Ident, ValType)>, crate::compiler_error::CompilerFailure> {
2048    params
2049        .iter()
2050        .enumerate()
2051        .map(|(i, p)| Ok((p.name.clone(), slot_param_type(ctx, slot_types, i, p)?)))
2052        .collect::<Result<_, crate::compiler_error::CompilerFailure>>()
2053}
2054
2055/// Unbox every param whose slot is erased but whose declared type is concrete
2056/// (an override of a generic method, a class-typed constructor param) into a
2057/// shadow local, and rebind the name to it so the body reads its own type.
2058/// Returns the local index backing each parameter, for the boxed-param
2059/// prologue. Every body this serves is self-first, so parameter `i` is local
2060/// `i + 1`.
2061fn rebind_erased_params(
2062    emitter: &mut FunctionEmitter<'_>,
2063    ctx: &CodegenCtx<'_>,
2064    params: &[crate::TypedParam],
2065    slot_types: &[ValType],
2066) -> Result<Vec<u32>, crate::compiler_error::CompilerFailure> {
2067    let mut param_slots = Vec::with_capacity(params.len());
2068    for (i, p) in params.iter().enumerate() {
2069        let param_local = parameter_local(i)?;
2070        let own_vt = ctx.symbols.value_type(&p.ty)?;
2071        if slot_param_type(ctx, slot_types, i, p)? == own_vt {
2072            param_slots.push(param_local);
2073            continue;
2074        }
2075        let shadow = emitter.add_anonymous_local(own_vt)?;
2076        emitter.instruction(Instruction::LocalGet(param_local));
2077        crate::codegen::cast_check::emit_checked_parameter_cast_on_stack(
2078            emitter,
2079            ctx,
2080            &crate::Type::Unknown,
2081            &p.ty,
2082        )?;
2083        emitter.instruction(Instruction::LocalSet(shadow));
2084        emitter.rebind_in_innermost_scope(&p.name.name, shadow, own_vt)?;
2085        param_slots.push(shadow);
2086    }
2087    Ok(param_slots)
2088}
2089
2090fn stub_body() -> Function {
2091    let mut f = Function::new([]);
2092    f.instruction(&Instruction::Unreachable);
2093    f.instruction(&Instruction::End);
2094    f
2095}
2096
2097/// An Error subclass's universal slot that forwards to the prelude `Error`
2098/// vtable's body for the same slot: `toString` renders the `name` and
2099/// `message` every subclass inherits, and `hash` uses identity.
2100fn emit_error_slot_body(
2101    symbols: &SymbolTable,
2102    intrinsics: IntrinsicTypeIndices,
2103    slot: u32,
2104    slot_fn_type: u32,
2105) -> Result<Function, CompilerFailure> {
2106    let vtable = symbols
2107        .class_vtable_global_idx(&crate::mangle::prelude("Error"))
2108        .ok_or_else(|| internal_failure("Error vtable is not imported for its subclass"))?;
2109    let mut body = Function::new([]);
2110    body.instruction(&Instruction::LocalGet(0));
2111    body.instruction(&Instruction::GlobalGet(vtable));
2112    body.instruction(&Instruction::StructGet {
2113        struct_type_index: intrinsics.vtable,
2114        field_index: slot,
2115    });
2116    body.instruction(&Instruction::CallRef(slot_fn_type));
2117    body.instruction(&Instruction::End);
2118    Ok(body)
2119}
2120
2121/// The class's `equals` vtable body: nominal exact-class guard + structural
2122/// compare of the data-field payload slots.
2123///
2124/// The guard is a `ref.eq` of the two operands' vtable refs — self's vtable
2125/// *is* this class's singleton (only this class's vtable references this
2126/// body), so no `global.get` is needed, and any other operand (sibling,
2127/// subclass, structural shape, boxed primitive) fails it. Only slots
2128/// `0..n_fields` are compared: the payload's trailing slots hold per-instance
2129/// method closures, whose reference-identity equals would break field-equal
2130/// instances.
2131fn emit_class_equals_body(n_fields: u32, intrinsics: IntrinsicTypeIndices) -> Function {
2132    // Locals layout (after 2 params):
2133    //   2: $elem_a    (ref null $Object)
2134    //   3: $elem_b    (ref null $Object)
2135    //   4: $elem_a_nn (ref $Object)
2136    //   5: $eq_fn     (ref $equalsFn)
2137    //   6: $a_fields  (ref $objectFields)
2138    //   7: $b_fields  (ref $objectFields)
2139    let locals: Vec<(u32, ValType)> = if n_fields == 0 {
2140        Vec::new()
2141    } else {
2142        vec![
2143            (
2144                2,
2145                ValType::Ref(RefType {
2146                    nullable: true,
2147                    heap_type: HeapType::Concrete(intrinsics.object),
2148                }),
2149            ),
2150            (1, ref_to(intrinsics.object)),
2151            (1, ref_to(intrinsics.equals_fn)),
2152            (2, ref_to(intrinsics.object_fields)),
2153        ]
2154    };
2155    let mut f = Function::new(locals);
2156    let (a, b) = (0u32, 1u32);
2157    let (a_fields, b_fields) = (6u32, 7u32);
2158
2159    f.instruction(&Instruction::LocalGet(a));
2160    f.instruction(&Instruction::LocalGet(b));
2161    f.instruction(&Instruction::RefEq);
2162    f.instruction(&Instruction::If(BlockType::Empty));
2163    f.instruction(&Instruction::I32Const(1));
2164    f.instruction(&Instruction::Return);
2165    f.instruction(&Instruction::End);
2166
2167    for operand in [a, b] {
2168        f.instruction(&Instruction::LocalGet(operand));
2169        f.instruction(&Instruction::StructGet {
2170            struct_type_index: intrinsics.object,
2171            field_index: 0,
2172        });
2173    }
2174    f.instruction(&Instruction::RefEq);
2175    f.instruction(&Instruction::I32Eqz);
2176    f.instruction(&Instruction::If(BlockType::Empty));
2177    f.instruction(&Instruction::I32Const(0));
2178    f.instruction(&Instruction::Return);
2179    f.instruction(&Instruction::End);
2180
2181    if n_fields > 0 {
2182        // Casts are guaranteed: the guard passed, so both operands are
2183        // instances of this class.
2184        for (operand, dest) in [(a, a_fields), (b, b_fields)] {
2185            f.instruction(&Instruction::LocalGet(operand));
2186            f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
2187                intrinsics.object_shape,
2188            )));
2189            f.instruction(&Instruction::StructGet {
2190                struct_type_index: intrinsics.object_shape,
2191                field_index: 2,
2192            });
2193            f.instruction(&Instruction::LocalSet(dest));
2194        }
2195        for slot in 0..n_fields {
2196            emit_payload_slot_equals(&mut f, slot, intrinsics);
2197        }
2198    }
2199
2200    f.instruction(&Instruction::I32Const(1));
2201    f.instruction(&Instruction::End);
2202    f
2203}
2204
2205/// One payload-slot comparison for [`emit_class_equals_body`], null-aware
2206/// (optional fields store `null`): both null → equal, one null → return 0,
2207/// else dispatch the element's own `vtable.equals`. Uses the fixed locals
2208/// documented there.
2209fn emit_payload_slot_equals(f: &mut Function, slot: u32, intrinsics: IntrinsicTypeIndices) {
2210    let (elem_a, elem_b, elem_a_nn, eq_fn) = (2u32, 3u32, 4u32, 5u32);
2211    let (a_fields, b_fields) = (6u32, 7u32);
2212
2213    for (fields, dest) in [(a_fields, elem_a), (b_fields, elem_b)] {
2214        f.instruction(&Instruction::LocalGet(fields));
2215        f.instruction(&Instruction::I32Const(slot.cast_signed()));
2216        f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
2217        f.instruction(&Instruction::LocalSet(dest));
2218    }
2219
2220    f.instruction(&Instruction::LocalGet(elem_a));
2221    f.instruction(&Instruction::RefIsNull);
2222    f.instruction(&Instruction::If(BlockType::Empty));
2223    f.instruction(&Instruction::LocalGet(elem_b));
2224    f.instruction(&Instruction::RefIsNull);
2225    f.instruction(&Instruction::I32Eqz);
2226    f.instruction(&Instruction::If(BlockType::Empty));
2227    f.instruction(&Instruction::I32Const(0));
2228    f.instruction(&Instruction::Return);
2229    f.instruction(&Instruction::End);
2230    f.instruction(&Instruction::Else);
2231    f.instruction(&Instruction::LocalGet(elem_b));
2232    f.instruction(&Instruction::RefIsNull);
2233    f.instruction(&Instruction::If(BlockType::Empty));
2234    f.instruction(&Instruction::I32Const(0));
2235    f.instruction(&Instruction::Return);
2236    f.instruction(&Instruction::End);
2237
2238    f.instruction(&Instruction::LocalGet(elem_a));
2239    f.instruction(&Instruction::RefAsNonNull);
2240    f.instruction(&Instruction::LocalTee(elem_a_nn));
2241    f.instruction(&Instruction::StructGet {
2242        struct_type_index: intrinsics.object,
2243        field_index: 0,
2244    });
2245    f.instruction(&Instruction::StructGet {
2246        struct_type_index: intrinsics.vtable,
2247        field_index: 2,
2248    });
2249    f.instruction(&Instruction::LocalSet(eq_fn));
2250
2251    f.instruction(&Instruction::LocalGet(elem_a_nn));
2252    f.instruction(&Instruction::LocalGet(elem_b));
2253    f.instruction(&Instruction::RefAsNonNull);
2254    f.instruction(&Instruction::LocalGet(eq_fn));
2255    f.instruction(&Instruction::CallRef(intrinsics.equals_fn));
2256    f.instruction(&Instruction::I32Eqz);
2257    f.instruction(&Instruction::If(BlockType::Empty));
2258    f.instruction(&Instruction::I32Const(0));
2259    f.instruction(&Instruction::Return);
2260    f.instruction(&Instruction::End);
2261    f.instruction(&Instruction::End);
2262}
2263
2264/// Bridge an authored conversion method to the universal string-returning slot.
2265/// Read its current payload so writes through structural aliases also affect
2266/// implicit conversions, including inherited methods with a different layout.
2267fn emit_universal_slot_thunk(
2268    ctx: &CodegenCtx,
2269    method: &str,
2270) -> Result<Function, crate::compiler_error::CompilerFailure> {
2271    let mut emitter = super::function_emitter::FunctionEmitter::new(
2272        ctx,
2273        &[(
2274            crate::Ident {
2275                name: "self".into(),
2276                span: crate::Span::at(crate::FileId(0)),
2277            },
2278            ctx.symbols.value_type(&crate::Type::Unknown)?,
2279        )],
2280    )?;
2281    emitter.instruction(Instruction::LocalGet(0));
2282    super::function_emitter::expr::emit_interface_method_via_shape_with_receiver_on_stack(
2283        &mut emitter,
2284        ctx,
2285        method,
2286        &[],
2287        &crate::Type::String,
2288        None,
2289    )?;
2290    emitter.build()
2291}
2292
2293fn emit_class_to_string_body(
2294    intrinsics: IntrinsicTypeIndices,
2295    string_vtable_global_idx: u32,
2296) -> Result<Function, CompilerFailure> {
2297    let mut f = Function::new([]);
2298    crate::codegen::intrinsics::push_string_literal(
2299        &mut f,
2300        intrinsics,
2301        string_vtable_global_idx,
2302        "[object Object]",
2303    )?;
2304    f.instruction(&Instruction::End);
2305    Ok(f)
2306}
2307
2308/// A class whose `toString` is a field converts through the host object vtable,
2309/// which calls the field when it holds a function and gives `[object Object]`
2310/// otherwise, as when an optional one is absent.
2311fn emit_class_to_string_field_body(
2312    symbols: &SymbolTable,
2313    intrinsics: IntrinsicTypeIndices,
2314) -> Result<Function, CompilerFailure> {
2315    let object_vtable = symbols
2316        .prelude_global_idx("object_vtable")
2317        .ok_or_else(|| internal_failure("object_vtable is not imported from the prelude"))?;
2318    let mut f = Function::new([]);
2319    f.instruction(&Instruction::LocalGet(0));
2320    f.instruction(&Instruction::GlobalGet(object_vtable));
2321    f.instruction(&Instruction::StructGet {
2322        struct_type_index: intrinsics.vtable,
2323        field_index: 0,
2324    });
2325    f.instruction(&Instruction::CallRef(intrinsics.to_string_fn));
2326    f.instruction(&Instruction::End);
2327    Ok(f)
2328}
2329
2330/// Class serialization reads the runtime property metadata, including visibility
2331/// and getters, so structural views and dynamically added fields use the same rules.
2332fn emit_class_to_json_body(symbols: &SymbolTable) -> Result<Function, CompilerFailure> {
2333    let serializer = symbols
2334        .prelude_func_idx("ObjectConstructor##toJson")
2335        .ok_or_else(|| internal_failure("the object serializer is not imported"))?;
2336    let mut f = Function::new([]);
2337    f.instruction(&Instruction::LocalGet(0));
2338    f.instruction(&Instruction::Call(serializer));
2339    f.instruction(&Instruction::End);
2340    Ok(f)
2341}
2342
2343/// Default class `hash`: FNV-1a over the data-field payload slots, each value
2344/// hashed through its own `vtable.hash` (null slot → 0) — the same recipe as
2345/// the structural `emit_subtype_hash_body`, so the `equals`-implies-equal-hash
2346/// contract holds: `equals` compares exactly these slots by vtable dispatch.
2347fn emit_class_hash_body(n_fields: u32, intrinsics: IntrinsicTypeIndices) -> Function {
2348    const FNV_BASIS: i32 = 0x811c9dc5_u32.cast_signed();
2349    const FNV_PRIME: i32 = 0x01000193;
2350
2351    if n_fields == 0 {
2352        let mut f = Function::new([]);
2353        f.instruction(&Instruction::I32Const(FNV_BASIS));
2354        f.instruction(&Instruction::End);
2355        return f;
2356    }
2357
2358    // Locals (after 1 param self=0):
2359    //   1: $fields  (ref $objectFields)
2360    //   2: $hash    (i32)
2361    //   3: $elem    (ref null $Object)
2362    //   4: $elem_nn (ref $Object)
2363    //   5: $hash_fn (ref $hashFn)
2364    let locals: Vec<(u32, ValType)> = vec![
2365        (1, ref_to(intrinsics.object_fields)),
2366        (1, ValType::I32),
2367        (
2368            1,
2369            ValType::Ref(RefType {
2370                nullable: true,
2371                heap_type: HeapType::Concrete(intrinsics.object),
2372            }),
2373        ),
2374        (1, ref_to(intrinsics.object)),
2375        (1, ref_to(intrinsics.hash_fn)),
2376    ];
2377    let mut f = Function::new(locals);
2378    let (fields_arr, hash, elem, elem_nn, hash_fn) = (1u32, 2u32, 3u32, 4u32, 5u32);
2379
2380    f.instruction(&Instruction::LocalGet(0));
2381    f.instruction(&Instruction::RefCastNonNull(HeapType::Concrete(
2382        intrinsics.object_shape,
2383    )));
2384    f.instruction(&Instruction::StructGet {
2385        struct_type_index: intrinsics.object_shape,
2386        field_index: 2,
2387    });
2388    f.instruction(&Instruction::LocalSet(fields_arr));
2389
2390    f.instruction(&Instruction::I32Const(FNV_BASIS));
2391    f.instruction(&Instruction::LocalSet(hash));
2392
2393    for slot in 0..n_fields {
2394        f.instruction(&Instruction::LocalGet(fields_arr));
2395        f.instruction(&Instruction::I32Const(slot.cast_signed()));
2396        f.instruction(&Instruction::ArrayGet(intrinsics.object_fields));
2397        f.instruction(&Instruction::LocalSet(elem));
2398
2399        f.instruction(&Instruction::LocalGet(elem));
2400        f.instruction(&Instruction::RefIsNull);
2401        f.instruction(&Instruction::If(BlockType::Result(ValType::I32)));
2402        f.instruction(&Instruction::I32Const(0));
2403        f.instruction(&Instruction::Else);
2404        f.instruction(&Instruction::LocalGet(elem));
2405        f.instruction(&Instruction::RefAsNonNull);
2406        f.instruction(&Instruction::LocalTee(elem_nn));
2407        f.instruction(&Instruction::StructGet {
2408            struct_type_index: intrinsics.object,
2409            field_index: 0,
2410        });
2411        f.instruction(&Instruction::StructGet {
2412            struct_type_index: intrinsics.vtable,
2413            field_index: 3,
2414        });
2415        f.instruction(&Instruction::LocalSet(hash_fn));
2416        f.instruction(&Instruction::LocalGet(elem_nn));
2417        f.instruction(&Instruction::LocalGet(hash_fn));
2418        f.instruction(&Instruction::CallRef(intrinsics.hash_fn));
2419        f.instruction(&Instruction::End);
2420
2421        // hash = (hash XOR slot_hash) * FNV prime
2422        f.instruction(&Instruction::LocalGet(hash));
2423        f.instruction(&Instruction::I32Xor);
2424        f.instruction(&Instruction::I32Const(FNV_PRIME));
2425        f.instruction(&Instruction::I32Mul);
2426        f.instruction(&Instruction::LocalSet(hash));
2427    }
2428
2429    f.instruction(&Instruction::LocalGet(hash));
2430    f.instruction(&Instruction::End);
2431    f
2432}
2433
2434/// Member names this module may write through a structural alias. A method is
2435/// fixed on its class, but a `{ m: () => T }` view of the instance, or a record
2436/// holding functions, writes the payload slot the method's closure lives in.
2437///
2438/// The rule is by name, so it covers imported classes too: this module's write
2439/// reaches an imported instance as easily as a local one.
2440#[derive(Clone, Debug)]
2441pub(crate) enum RewritableMembers {
2442    Named(std::collections::BTreeSet<String>),
2443    /// A computed-key write of a function-capable value may reach any member.
2444    Every,
2445}
2446
2447impl Default for RewritableMembers {
2448    fn default() -> Self {
2449        Self::Named(std::collections::BTreeSet::new())
2450    }
2451}
2452
2453impl RewritableMembers {
2454    pub(crate) fn of(ta: &TypedAst) -> Result<Self, crate::compiler_error::CompilerFailure> {
2455        let mut members = Self::default();
2456        for id in ta.stmt_ids().map_err(super::arena_failure)? {
2457            match &ta.try_stmt(id).map_err(super::arena_failure)?.kind {
2458                crate::TypedStmtKind::AssignField { name, .. } => {
2459                    members.note_name(&name.name);
2460                }
2461                crate::TypedStmtKind::AssignIndex { index, elem_ty, .. } => {
2462                    members.note_index(ta, *index, elem_ty)?;
2463                }
2464                _ => {}
2465            }
2466        }
2467        for id in ta.expr_ids().map_err(super::arena_failure)? {
2468            let crate::TypedExprKind::PostfixUnary { target, .. } =
2469                &ta.try_expr(id).map_err(super::arena_failure)?.kind
2470            else {
2471                continue;
2472            };
2473            match target {
2474                crate::PostfixTarget::Field { name, .. } => {
2475                    members.note_name(&name.name);
2476                }
2477                crate::PostfixTarget::Index { index, elem_ty, .. } => {
2478                    members.note_index(ta, *index, elem_ty)?;
2479                }
2480                _ => {}
2481            }
2482        }
2483        Ok(members)
2484    }
2485
2486    fn note_index(
2487        &mut self,
2488        ta: &TypedAst,
2489        index: crate::ExprId,
2490        elem_ty: &crate::Type,
2491    ) -> Result<(), crate::compiler_error::CompilerFailure> {
2492        if !may_hold_function(elem_ty) {
2493            return Ok(());
2494        }
2495        match &ta.try_expr(index).map_err(super::arena_failure)?.kind {
2496            crate::TypedExprKind::String(name) => self.note_name(name),
2497            _ => *self = Self::Every,
2498        }
2499        Ok(())
2500    }
2501
2502    fn note_name(&mut self, name: &str) {
2503        if let Self::Named(names) = self {
2504            names.insert(name.to_string());
2505        }
2506    }
2507
2508    pub(crate) fn includes(&self, name: &str) -> bool {
2509        match self {
2510            Self::Named(names) => names.contains(name),
2511            Self::Every => true,
2512        }
2513    }
2514}
2515
2516fn may_hold_function(ty: &crate::Type) -> bool {
2517    match ty.peel() {
2518        crate::Type::Union(members) => members.iter().any(may_hold_function),
2519        crate::Type::Function { .. }
2520        | crate::Type::Unknown
2521        | crate::Type::NonPrimitive
2522        | crate::Type::TypeVar(_)
2523        | crate::Type::GenericParam { .. }
2524        | crate::Type::Error => true,
2525        _ => false,
2526    }
2527}
2528
2529fn ref_to(idx: u32) -> ValType {
2530    ValType::Ref(RefType {
2531        nullable: false,
2532        heap_type: HeapType::Concrete(idx),
2533    })
2534}
2535
2536fn fieldtype_ref(idx: u32) -> FieldType {
2537    FieldType {
2538        element_type: StorageType::Val(ref_to(idx)),
2539        mutable: false,
2540    }
2541}
2542
2543fn fieldtype_ref_null(idx: u32) -> FieldType {
2544    FieldType {
2545        element_type: StorageType::Val(ValType::Ref(RefType {
2546            nullable: true,
2547            heap_type: HeapType::Concrete(idx),
2548        })),
2549        mutable: false,
2550    }
2551}
2552
2553/// The Wasm supertype of a class's vtable: the parent class's vtable, or
2554/// `$ClassVTable` at the root of the chain.
2555fn vtable_supertype_idx(
2556    class: &ClassLayout,
2557    intrinsics: IntrinsicTypeIndices,
2558    symbols: &SymbolTable,
2559) -> Result<u32, CompilerFailure> {
2560    match &class.parent {
2561        Some(p) => symbols.class_vtable_type_idx(p).ok_or_else(|| {
2562            internal_failure("a parent vtable type was not reserved before its child")
2563        }),
2564        None => Ok(intrinsics.class_vtable),
2565    }
2566}
2567
2568/// The Wasm supertype of a class's instance struct: the parent class's struct,
2569/// or `$ObjectShape` at the root of the chain.
2570fn struct_supertype_idx(
2571    class: &ClassLayout,
2572    intrinsics: IntrinsicTypeIndices,
2573    symbols: &SymbolTable,
2574) -> Result<u32, CompilerFailure> {
2575    match &class.parent {
2576        Some(p) => symbols.class_struct_type_idx(p).ok_or_else(|| {
2577            internal_failure("a parent struct type was not reserved before its child")
2578        }),
2579        None => Ok(intrinsics.object_shape),
2580    }
2581}
2582
2583fn substruct(fields: Vec<FieldType>, supertype: Option<u32>) -> SubType {
2584    SubType {
2585        is_final: false,
2586        supertype_idx: supertype,
2587        composite_type: CompositeType {
2588            inner: CompositeInnerType::Struct(StructType {
2589                fields: fields.into_boxed_slice(),
2590            }),
2591            shared: false,
2592            descriptor: None,
2593            describes: None,
2594        },
2595    }
2596}
2597
2598#[cfg(test)]
2599mod tests {
2600    use super::*;
2601    use crate::codegen::intrinsics::declare_intrinsic_types;
2602    use crate::runtime::intrinsic_types::build_intrinsic_types;
2603    use wasm_encoder::{
2604        ConstExpr, ExportKind, ExportSection, GlobalSection, GlobalType, HeapType, Module, RefType,
2605        TypeSection, ValType,
2606    };
2607    use wasmtime::Config;
2608
2609    /// Seam guard for the intrinsic `(rec $Error_vtable $Error)` pair: this
2610    /// emitter's output for a parentless, method-less `{message, name}` class must
2611    /// canonicalize to the intrinsic types, so `class MyError extends Error`
2612    /// reconstructs the parent through the ordinary class path and WasmGC
2613    /// canonicalization unifies them.
2614    #[test]
2615    fn field_only_class_rec_group_canonicalizes_to_intrinsic_error() {
2616        let mut types = TypeSection::new();
2617        let intrinsics = declare_intrinsic_types(&mut types);
2618        let vtable_type_idx = crate::codegen::intrinsics::INTRINSIC_TYPE_COUNT;
2619        let struct_type_idx = vtable_type_idx + 1;
2620        let layout = ClassLayout {
2621            private_members: BTreeSet::new(),
2622            generics: Vec::new(),
2623            mangled: crate::mangle::prelude("ErrorWitness"),
2624            name: "ErrorWitness".to_string(),
2625            name_span: crate::Span::at(crate::FileId(0)),
2626            parent: None,
2627            fields: vec![
2628                FieldLayout {
2629                    name: "message".to_string(),
2630                    optional: false,
2631                    narrowing_check: None,
2632                },
2633                FieldLayout {
2634                    name: "name".to_string(),
2635                    optional: false,
2636                    narrowing_check: None,
2637                },
2638            ],
2639            methods: Vec::new(),
2640            own_methods: Vec::new(),
2641            adapter_slots: Vec::new(),
2642            ctor_params: Vec::new(),
2643            ctor_body: None,
2644            field_setup: Vec::new(),
2645            accessor_names: Vec::new(),
2646            struct_type_idx,
2647            vtable_type_idx,
2648            ctor_func_idx: 0,
2649            ctor_sig_idx: 0,
2650            ctor_init_func_idx: 0,
2651            ctor_init_sig_idx: 0,
2652            getter_func_idx: 0,
2653            setter_func_idx: 0,
2654            equals_func_idx: 0,
2655            to_string_func_idx: 0,
2656            to_json_func_idx: 0,
2657            hash_func_idx: 0,
2658            guarded_bodies: GuardedBodies::default(),
2659        };
2660        let plan = ClassPlan {
2661            package_name: "witness".to_string(),
2662            classes: Vec::new(),
2663            universal_stubs: [0; UNIVERSAL_STUB_COUNT],
2664        };
2665        let symbols = SymbolTable::default();
2666        let vtable = plan.vtable_subtype(&layout, intrinsics, &symbols).unwrap();
2667        let strukt = plan.struct_subtype(&layout, intrinsics, &symbols).unwrap();
2668        types.ty().rec([vtable, strukt]);
2669
2670        let mut module = Module::new();
2671        module.section(&types);
2672        let mut globals = GlobalSection::new();
2673        let mut exports = ExportSection::new();
2674        for (name, idx) in [("vtable", vtable_type_idx), ("struct", struct_type_idx)] {
2675            globals.global(
2676                GlobalType {
2677                    val_type: ValType::Ref(RefType {
2678                        nullable: true,
2679                        heap_type: HeapType::Concrete(idx),
2680                    }),
2681                    mutable: false,
2682                    shared: false,
2683                },
2684                &ConstExpr::ref_null(HeapType::Concrete(idx)),
2685            );
2686            exports.export(name, ExportKind::Global, idx - vtable_type_idx);
2687        }
2688        module.section(&globals);
2689        module.section(&exports);
2690
2691        let mut config = Config::new();
2692        config.wasm_gc(true);
2693        config.wasm_function_references(true);
2694        let engine = wasmtime::Engine::new(&config).unwrap();
2695        let intr = build_intrinsic_types(&engine).unwrap();
2696        let module = wasmtime::Module::new(&engine, module.finish()).unwrap();
2697        let recovered = |name: &str| {
2698            module
2699                .get_export(name)
2700                .unwrap()
2701                .global()
2702                .unwrap()
2703                .content()
2704                .as_ref()
2705                .map(|r| r.heap_type().clone())
2706                .unwrap()
2707                .as_concrete_struct()
2708                .unwrap()
2709                .clone()
2710        };
2711        assert!(wasmtime::StructType::eq(
2712            &intr.error_vtable,
2713            &recovered("vtable")
2714        ));
2715        assert!(wasmtime::StructType::eq(&intr.error, &recovered("struct")));
2716    }
2717}
2718
2719#[cfg(test)]
2720mod closure_adapter_invariant_tests {
2721    use super::*;
2722    use crate::codegen::invariant_tests::{assert_internal, with_context};
2723
2724    #[test]
2725    fn class_closure_adapter_requires_registered_method_and_slot_abi() {
2726        let (ta, diagnostics) = crate::compile::typecheck_to_typed_ast(
2727            "class C { f(a: number): number { return a; } } function main(): number { return new C().f(1); }",
2728            crate::FileId(0),
2729        );
2730        assert!(diagnostics.is_empty(), "{diagnostics:?}");
2731        let plan = ClassPlan::collect(&ta, &BTreeMap::new()).unwrap();
2732        let class = &plan.classes[0];
2733        let slot = &class.methods[0];
2734        let mut symbols = crate::codegen::tests::mock_symbols_with_intrinsics();
2735        with_context(&ta, &symbols, |ctx| {
2736            assert_internal(plan.emit_method_adapter_body(class, slot, ctx).unwrap_err());
2737        });
2738        symbols.record_class_method_func(slot.owner.clone(), slot.name.clone(), 1);
2739        with_context(&ta, &symbols, |ctx| {
2740            assert_internal(plan.emit_method_adapter_body(class, slot, ctx).unwrap_err());
2741        });
2742        symbols.record_class_method_abi(
2743            class.mangled.clone(),
2744            slot.name.clone(),
2745            MethodSlotAbi {
2746                params: vec![],
2747                ret: Some(ValType::F64),
2748            },
2749        );
2750        with_context(&ta, &symbols, |ctx| {
2751            assert_internal(plan.emit_method_adapter_body(class, slot, ctx).unwrap_err());
2752        });
2753    }
2754}
2755
2756#[cfg(test)]
2757mod layout_invariant_tests {
2758    use super::*;
2759    use crate::compiler_error::CompilerFailure;
2760
2761    fn typed(source: &str) -> TypedAst {
2762        let (ta, diagnostics) = crate::compile::typecheck_to_typed_ast(source, crate::FileId(0));
2763        assert!(diagnostics.is_empty(), "{diagnostics:?}");
2764        ta
2765    }
2766
2767    fn class_mut<'a>(ta: &'a mut TypedAst, name: &str) -> &'a mut TypedClassDecl {
2768        ta.types
2769            .iter_mut()
2770            .find_map(|decl| match decl {
2771                TypedTypeDecl::Class(class) if class.name.name == name => Some(class),
2772                _ => None,
2773            })
2774            .unwrap()
2775    }
2776
2777    fn assert_internal(result: Result<ClassPlan, CompilerFailure>) {
2778        assert!(
2779            matches!(result, Err(CompilerFailure::Internal { .. })),
2780            "expected an internal failure"
2781        );
2782    }
2783
2784    #[test]
2785    fn missing_or_cyclic_parents_stop_layout_instead_of_dropping_the_prefix() {
2786        let source = "class A { x: number = 1 } class B extends A { y: number = 2 } \
2787                      function main(): number { return new B().x; }";
2788        let intact = typed(source);
2789        let plan = ClassPlan::collect(&intact, &BTreeMap::new()).unwrap();
2790        let b = plan.classes.iter().find(|class| class.name == "B").unwrap();
2791        assert_eq!(b.fields.len(), 2, "B inherits A's field prefix");
2792
2793        // An orphaned subclass must fail rather than lose its parent's prefix.
2794        let mut orphan = intact.clone();
2795        orphan
2796            .types
2797            .retain(|decl| !matches!(decl, TypedTypeDecl::Class(class) if class.name.name == "A"));
2798        assert_internal(ClassPlan::collect(&orphan, &BTreeMap::new()));
2799
2800        let mut cyclic = intact;
2801        let b_name = class_mut(&mut cyclic, "B").mangled_name.clone();
2802        class_mut(&mut cyclic, "A").extends = Some(b_name);
2803        assert_internal(ClassPlan::collect(&cyclic, &BTreeMap::new()));
2804    }
2805
2806    #[test]
2807    fn guard_payload_arithmetic_is_checked() {
2808        assert_eq!(guard_slot_count(0, 0).unwrap(), 1);
2809        assert_eq!(guard_slot_count(2, 3).unwrap(), 12);
2810        assert!(guard_slot_count(u32::MAX, 0).is_err());
2811        assert!(guard_slot_count(1, u32::MAX).is_err());
2812        assert!(guard_slot_count(u32::MAX / 2, 3).is_err());
2813    }
2814}