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