Skip to main content

submilli_engine/codegen/
imported_classes.rs

1//! Cross-package class reconstruction (SUB-488).
2//!
3//! A consumer that imports a `class` from another package never sees that
4//! class's Wasm module — only its public `PackageDeclaration`. To use the class
5//! (construct it, read fields, dispatch methods, `instanceof`, or `extends` it)
6//! the consumer must rebuild the class's WasmGC types in its own module so that
7//! structural canonicalization unifies them with the producer's.
8//!
9//! Each class is emitted as its own rec group `(rec $C_vtable $C)` — matching the
10//! producer ([`super::classes`]) — so the consumer reconstructs only the classes
11//! it transitively needs, parents first. There are no cross-class type
12//! references (a class's fields live in the shared object-fields payload, not as
13//! named struct fields), so per-class groups always canonicalize. The consumer
14//! also imports each class's constructor, self-first ctor-init, and method bodies
15//! (exported by the producer in [`super::classes::ClassPlan::exported_funcs`]) so
16//! `new`, `super(...)`, and a local subclass's inherited vtable slots resolve.
17
18use std::collections::{BTreeMap, BTreeSet};
19
20use wasm_encoder::{
21    EntityType, FieldType, GlobalType, HeapType, ImportSection, RefType, StorageType, StructType,
22    SubType, TypeSection, ValType,
23};
24
25use crate::codegen::classes::{method_vtable_slot, shadows_inherited_field};
26use crate::codegen::dependency_usage::DependencyUsage;
27use crate::codegen::intrinsics::IntrinsicTypeIndices;
28use crate::codegen::symbol_table::{MethodSlotAbi, SymbolTable};
29use crate::codegen::{internal_failure, next_index, wasm_u32};
30use crate::compiler_error::CompilerFailure;
31use crate::{MangledName, Param, TypeKind, TypedAst, TypedTypeDecl};
32
33/// The resolved full layout of an imported class, handed to [`super::classes`]
34/// so a local subclass extending it lays its own fields/methods out after the
35/// inherited prefix.
36pub struct ImportedClassLayout {
37    pub private_members: BTreeSet<String>,
38    /// Full data-field names (inherited prefix then own), object-payload order.
39    pub fields: Vec<String>,
40    pub optional_fields: std::collections::BTreeSet<String>,
41    pub narrowing_checks: BTreeMap<String, crate::FieldNarrowingCheck>,
42    /// Full vtable method slots (inherited prefix then own/override), in order.
43    pub methods: Vec<ImportedSlot>,
44    /// The class's own constructor params, so a local subclass with an *implicit*
45    /// constructor extending this class inherits its parameter signature.
46    pub ctor_params: Vec<crate::TypedParam>,
47}
48
49pub struct ImportedSlot {
50    pub name: String,
51    /// The class whose body backs this slot (the declarer, or nearest ancestor
52    /// for an inherited slot) — the key under which its method func was imported.
53    pub owner: MangledName,
54    /// Declared parameter/return types of the owner's body, so a local subclass
55    /// can emit its own payload adapter for this slot.
56    pub param_tys: Vec<crate::Type>,
57    pub argument_metadata: Option<String>,
58    pub ret_ty: crate::Type,
59    pub generic: bool,
60}
61
62/// Per-class data gathered from the dependency `PackageDeclaration`s. `fields`
63/// and `methods` are `BTreeMap`s, so iterating their keys yields the canonical
64/// (sorted) own-member order the producer assigns slots in — no order metadata
65/// has to cross the package boundary.
66struct ClassInfo<'a> {
67    generics: &'a [String],
68    package: &'a str,
69    compiled_class: bool,
70    runtime_generics: &'a BTreeSet<MangledName>,
71    name: &'a str,
72    extends: Option<MangledName>,
73    fields: &'a BTreeMap<String, crate::FieldSig>,
74    narrowing_checks: &'a BTreeMap<String, crate::FieldNarrowingCheck>,
75    methods: &'a BTreeMap<String, crate::MethodSig>,
76    constructor: &'a [Param],
77    /// Accessor functions; codegen reconstructs the same synthetic getter/setter
78    /// vtable methods the producer emitted.
79    accessors: &'a [crate::AccessorSig],
80    statics: &'a BTreeMap<String, crate::MethodSig>,
81    static_visibility: &'a BTreeMap<String, crate::Visibility>,
82    static_fields: &'a BTreeMap<String, crate::FieldSig>,
83}
84
85impl<'a> ClassInfo<'a> {
86    /// Private statics are not exported by the producer, so the consumer must
87    /// not import them.
88    fn public_statics(&self) -> impl Iterator<Item = (&'a String, &'a crate::MethodSig)> {
89        self.statics.iter().filter(|(name, _)| {
90            self.static_visibility.get(*name) != Some(&crate::Visibility::Private)
91        })
92    }
93
94    fn public_static_fields(&self) -> impl Iterator<Item = (&'a String, &'a crate::FieldSig)> {
95        self.static_fields
96            .iter()
97            .filter(|(_, field)| field.visibility != crate::Visibility::Private)
98    }
99}
100
101/// The producer's vtable-method set for an imported class: real methods plus the
102/// synthetic getter/setter methods derived from its accessors, each as
103/// `(name, params, ret)`. Mirrors `codegen::classes::accessor_methods` so slot
104/// indices and signatures line up across the package boundary.
105fn vtable_methods(class: &ClassInfo<'_>) -> BTreeMap<String, (Vec<crate::Type>, crate::Type)> {
106    let mut out: BTreeMap<String, (Vec<crate::Type>, crate::Type)> = class
107        .methods
108        .iter()
109        .map(|(name, sig)| {
110            (
111                name.clone(),
112                (
113                    sig.params.iter().map(|p| p.ty.clone()).collect(),
114                    sig.ret.clone(),
115                ),
116            )
117        })
118        .collect();
119    for acc in class.accessors {
120        match acc {
121            crate::AccessorSig::Getter { name, ret_ty } => {
122                out.insert(
123                    crate::codegen::classes::accessor_getter_name(name),
124                    (Vec::new(), ret_ty.clone()),
125                );
126            }
127            crate::AccessorSig::Setter { name, param } => {
128                out.insert(
129                    crate::codegen::classes::accessor_setter_name(name),
130                    (vec![param.ty.clone()], crate::Type::Void),
131                );
132            }
133        }
134    }
135    out
136}
137
138/// The linker module a class's function bodies import from — the owning
139/// package for user classes and prelude-declared (host-implemented) classes
140/// alike: `Error`'s constructor/ctor-init are Rust host fns registered under
141/// `submilli:prelude`.
142fn host_module_for<'a>(class: &ClassInfo<'a>) -> &'a str {
143    class.package
144}
145
146/// Reconstruct every imported class reachable from this module: emit its rec
147/// group, record type/slot indices, and import its functions. Returns the
148/// resolved layout of each, keyed by mangled name, for `classes::ClassPlan`.
149#[allow(clippy::too_many_arguments)]
150pub fn reconstruct(
151    dependencies: &[&crate::PackageDeclaration],
152    ta: &TypedAst,
153    usage: &DependencyUsage,
154    intrinsics: IntrinsicTypeIndices,
155    file: crate::FileId,
156    types: &mut TypeSection,
157    next_type_idx: &mut u32,
158    imports: &mut ImportSection,
159    next_func_idx: &mut u32,
160    next_global_idx: &mut u32,
161    symbols: &mut SymbolTable,
162) -> Result<BTreeMap<MangledName, ImportedClassLayout>, crate::compiler_error::CompilerFailure> {
163    let info = collect_class_info(dependencies);
164    let order = needed_in_topo_order(&info, ta, usage)?;
165
166    let mut layouts: BTreeMap<MangledName, ImportedClassLayout> = BTreeMap::new();
167    for mangled in &order {
168        let class = class_info(&info, mangled)?;
169        let layout = reconstruct_one(
170            mangled,
171            class,
172            &info,
173            &layouts,
174            intrinsics,
175            file,
176            types,
177            next_type_idx,
178            imports,
179            next_func_idx,
180            next_global_idx,
181            symbols,
182        )?;
183        layouts.insert(mangled.clone(), layout);
184    }
185    // Statics come last — see `import_statics`.
186    for mangled in &order {
187        import_statics(
188            mangled,
189            class_info(&info, mangled)?,
190            usage,
191            types,
192            next_type_idx,
193            imports,
194            next_func_idx,
195            next_global_idx,
196            symbols,
197        )?;
198    }
199    Ok(layouts)
200}
201
202/// Import the statics this module actually uses: self-less functions and
203/// backing globals, keyed `Class#static#name`.
204///
205/// Unlike methods and constructors, these signatures are **unerased** — the
206/// producer emits statics through the ordinary function/global path, where
207/// `value_type` resolves a class to its concrete struct. So every class one
208/// names must already be reconstructed, or `value_type` takes the
209/// `(ref null $Object)` fallback and the import fails to link.
210///
211/// Two things keep that true. This runs in a pass after all reconstruction, so
212/// declaration order can't matter. And the usage gate holds the rest: resolving
213/// a static marks every class its signature names, so one that reaches here has
214/// already pulled the classes it names *from this module's dependency closure*
215/// into the reconstructed set. A class from outside that closure is marked but
216/// never reconstructed, and still takes the fallback — the residual hole is a
217/// consumer that declares fewer dependencies than the statics it uses reach.
218///
219/// Importing *unused* statics is what would break the invariant outright: a
220/// class reconstructed only as someone's ancestor carries statics nothing
221/// prepared types for, and they need not even be expressible here — a
222/// function-typed static wants a closure struct this module may never have
223/// registered.
224#[allow(clippy::too_many_arguments)]
225fn import_statics(
226    mangled: &MangledName,
227    class: &ClassInfo<'_>,
228    usage: &DependencyUsage,
229    types: &mut TypeSection,
230    next_type_idx: &mut u32,
231    imports: &mut ImportSection,
232    next_func_idx: &mut u32,
233    next_global_idx: &mut u32,
234    symbols: &mut SymbolTable,
235) -> Result<(), crate::compiler_error::CompilerFailure> {
236    for (name, sig) in class.public_statics() {
237        let key = crate::mangle::static_member(mangled, name);
238        if !usage.is_value_used(&key) {
239            continue;
240        }
241        let mut param_types: Vec<ValType> = sig
242            .params
243            .iter()
244            .map(|p| symbols.value_type(&p.ty))
245            .collect::<Result<_, _>>()?;
246        if class.runtime_generics.contains(&key) {
247            symbols.runtime_generic_functions.insert(key.clone());
248            param_types.push(super::runtime_descriptors::environment_type(symbols)?);
249        }
250        let results = symbols.wasm_result(&sig.ret)?;
251        let sig_idx = next_index(next_type_idx)?;
252        types.ty().function(param_types, results);
253        imports.import(
254            host_module_for(class),
255            key.as_str(),
256            EntityType::Function(sig_idx),
257        );
258        let idx = next_index(next_func_idx)?;
259        symbols.record_imported_fn(
260            key,
261            idx,
262            sig.params.iter().map(|p| p.ty.clone()).collect(),
263            sig.ret.clone(),
264            false,
265        );
266    }
267    for (name, field) in class.public_static_fields() {
268        let key = crate::mangle::static_member(mangled, name);
269        if !usage.is_value_used(&key) {
270            continue;
271        }
272        // Mutable like every exported value-global — the producer's `_start`
273        // initializes it; const-ness is typechecker-enforced.
274        imports.import(
275            host_module_for(class),
276            key.as_str(),
277            EntityType::Global(GlobalType {
278                val_type: super::global_val_type(&field.ty, symbols)?,
279                mutable: true,
280                shared: false,
281            }),
282        );
283        symbols.record_typed_global(key, next_index(next_global_idx)?, field.ty.clone());
284    }
285    Ok(())
286}
287
288fn collect_class_info<'a>(
289    dependencies: &'a [&'a crate::PackageDeclaration],
290) -> BTreeMap<MangledName, ClassInfo<'a>> {
291    let mut info = BTreeMap::new();
292    for defs in dependencies {
293        for sym in defs.runtime_types.values().chain(defs.types.values()) {
294            if let TypeKind::Class {
295                generics,
296                extends,
297                fields,
298                narrowing_checks,
299                methods,
300                accessors,
301                constructor,
302                statics,
303                static_visibility,
304                static_fields,
305                ..
306            } = &sym.kind
307            {
308                info.insert(
309                    sym.mangled_name.clone(),
310                    ClassInfo {
311                        generics,
312                        runtime_generics: &defs.runtime_generics,
313                        package: defs.package_name.as_str(),
314                        compiled_class: defs.runtime_types.contains_key(sym.mangled_name.as_str()),
315                        name: sym.name.as_str(),
316                        // Layout and vtables depend on the parent's name;
317                        // concrete arguments travel in the instance context.
318                        extends: extends.as_ref().map(|e| e.parent.clone()),
319                        fields,
320                        narrowing_checks,
321                        methods,
322                        constructor,
323                        accessors,
324                        statics,
325                        static_visibility,
326                        static_fields,
327                    },
328                );
329            }
330        }
331    }
332    info
333}
334
335/// Imported classes the module needs: every imported class referenced by the
336/// program plus the imported parents of local classes, transitively closed over
337/// `extends`, ordered parents-first.
338fn needed_in_topo_order(
339    info: &BTreeMap<MangledName, ClassInfo<'_>>,
340    ta: &TypedAst,
341    usage: &DependencyUsage,
342) -> Result<Vec<MangledName>, CompilerFailure> {
343    let mut roots: BTreeSet<MangledName> = BTreeSet::new();
344    for mangled in info.keys() {
345        // `is_type_reachable_by_name` also catches classes used only through
346        // their statics (never constructed, never named as a type).
347        if usage.is_type_reachable_by_name(mangled) {
348            roots.insert(mangled.clone());
349        }
350    }
351    // Imported parents of local classes (the parent itself may not be "used").
352    for decl in &ta.types {
353        if let TypedTypeDecl::Class(c) = decl
354            && let Some(parent) = &c.extends
355            && info.contains_key(parent)
356        {
357            roots.insert(parent.clone());
358        }
359    }
360
361    let mut order: Vec<MangledName> = Vec::new();
362    let mut seen: BTreeSet<&MangledName> = BTreeSet::new();
363    for root in &roots {
364        push_ancestors_first(info, root, &mut seen, &mut order)?;
365    }
366    Ok(order)
367}
368
369/// Appends `root` and its not-yet-ordered ancestors, parents first. Unlike a
370/// local class, an imported class's ancestors must all be declared: without
371/// one, the subclass would be reconstructed without its field and slot prefix
372/// and fail to link as `imported global type mismatch`, naming nothing.
373fn push_ancestors_first<'a>(
374    info: &'a BTreeMap<MangledName, ClassInfo<'_>>,
375    root: &'a MangledName,
376    seen: &mut BTreeSet<&'a MangledName>,
377    order: &mut Vec<MangledName>,
378) -> Result<(), CompilerFailure> {
379    let mut chain: Vec<&MangledName> = Vec::new();
380    let mut next = Some(root);
381    while let Some(mangled) = next {
382        if seen.contains(mangled) {
383            break;
384        }
385        let class = info.get(mangled).ok_or_else(|| {
386            internal_failure(format!(
387                "the dependency closure passed to codegen has no declaration for \
388                 `{mangled}`, an ancestor of imported class `{root}`"
389            ))
390        })?;
391        if chain.contains(&mangled) {
392            return Err(internal_failure(format!(
393                "imported class `{mangled}` inherits from itself"
394            )));
395        }
396        chain.push(mangled);
397        next = class.extends.as_ref();
398    }
399    for mangled in chain.into_iter().rev() {
400        seen.insert(mangled);
401        order.push(mangled.clone());
402    }
403    Ok(())
404}
405
406fn class_info<'i, 'a>(
407    info: &'i BTreeMap<MangledName, ClassInfo<'a>>,
408    mangled: &MangledName,
409) -> Result<&'i ClassInfo<'a>, CompilerFailure> {
410    info.get(mangled).ok_or_else(|| {
411        internal_failure(format!(
412            "imported class `{mangled}` has no reconstructed declaration"
413        ))
414    })
415}
416
417#[allow(clippy::too_many_arguments)]
418fn reconstruct_one(
419    mangled: &MangledName,
420    class: &ClassInfo<'_>,
421    info: &BTreeMap<MangledName, ClassInfo<'_>>,
422    layouts: &BTreeMap<MangledName, ImportedClassLayout>,
423    intrinsics: IntrinsicTypeIndices,
424    file: crate::FileId,
425    types: &mut TypeSection,
426    next_type_idx: &mut u32,
427    imports: &mut ImportSection,
428    next_func_idx: &mut u32,
429    next_global_idx: &mut u32,
430    symbols: &mut SymbolTable,
431) -> Result<ImportedClassLayout, crate::compiler_error::CompilerFailure> {
432    // Parents are reconstructed first (`needed_in_topo_order`).
433    let parent_layout = match &class.extends {
434        Some(parent) => Some(layouts.get(parent).ok_or_else(|| {
435            internal_failure(format!(
436                "imported class `{mangled}` was reconstructed before its parent `{parent}`"
437            ))
438        })?),
439        None => None,
440    };
441    // Full field list = inherited prefix (already reconstructed) + own *data*
442    // fields. Accessor properties are in `fields` for typing/docs but back no data
443    // slot, so they're excluded — matching the producer's payload layout.
444    let mut fields: Vec<String> = parent_layout.map(|l| l.fields.clone()).unwrap_or_default();
445    for name in class
446        .fields
447        .keys()
448        .filter(|name| !class.accessors.iter().any(|a| a.name() == name.as_str()))
449    {
450        if shadows_inherited_field(fields.iter().map(String::as_str), name) {
451            continue;
452        }
453        fields.push(name.clone());
454    }
455    let mut optional_fields = parent_layout
456        .map(|layout| layout.optional_fields.clone())
457        .unwrap_or_default();
458    for (name, field) in class.fields {
459        if field.optional {
460            optional_fields.insert(name.clone());
461        } else {
462            optional_fields.remove(name);
463        }
464    }
465    let mut narrowing_checks = parent_layout
466        .map(|layout| layout.narrowing_checks.clone())
467        .unwrap_or_default();
468    narrowing_checks.extend(class.narrowing_checks.clone());
469
470    // The producer's full vtable-method set (real methods + accessor getter/setter),
471    // sorted by name to match the producer's slot assignment.
472    let methods = vtable_methods(class);
473
474    // Full vtable slots = parent's slots, then fold in own methods (override →
475    // same slot/owner-of-body, new → appended owned by this class).
476    let mut slots: Vec<ImportedSlot> = parent_layout
477        .map(|l| {
478            l.methods
479                .iter()
480                .map(|s| ImportedSlot {
481                    name: s.name.clone(),
482                    owner: s.owner.clone(),
483                    param_tys: s.param_tys.clone(),
484                    argument_metadata: s.argument_metadata.clone(),
485                    ret_ty: s.ret_ty.clone(),
486                    generic: s.generic,
487                })
488                .collect()
489        })
490        .unwrap_or_default();
491    for (name, (param_tys, ret_ty)) in &methods {
492        let argument_metadata = match class.methods.get(name) {
493            Some(sig) => super::call_arguments::metadata(sig.params.iter().map(|param| {
494                (
495                    param
496                        .default
497                        .as_ref()
498                        .or(param.optional.then_some(&crate::DefaultValue::Undefined)),
499                    param.rest,
500                )
501            })),
502            None => None,
503        };
504        let generic = class
505            .methods
506            .get(name)
507            .is_some_and(|sig| !sig.generics.is_empty());
508        if let Some(slot) = slots.iter_mut().find(|s| &s.name == name) {
509            // Override: this class supplies the body, so the slot must describe
510            // it rather than the ancestor's.
511            slot.owner = mangled.clone();
512            slot.param_tys = param_tys.clone();
513            slot.argument_metadata = argument_metadata;
514            slot.ret_ty = ret_ty.clone();
515            slot.generic = generic;
516        } else {
517            slots.push(ImportedSlot {
518                name: name.clone(),
519                owner: mangled.clone(),
520                param_tys: param_tys.clone(),
521                argument_metadata,
522                ret_ty: ret_ty.clone(),
523                generic,
524            });
525        }
526    }
527
528    // 1. Method-signature fn-types for slots that *originate* on this class
529    //    (newly introduced). Overrides/inherited slots reuse the originating
530    //    ancestor's sig, looked up from the symbol table (parents come first).
531    for (name, (param_tys, ret)) in &methods {
532        let originates = class
533            .extends
534            .as_ref()
535            .and_then(|p| symbols.class_method_sig(p, name))
536            .is_none();
537        if !originates {
538            continue;
539        }
540        let mut params: Vec<ValType> = vec![ref_to(intrinsics.object)];
541        params.extend(
542            param_tys
543                .iter()
544                .map(|_| symbols.value_type(&crate::Type::Unknown))
545                .collect::<Result<Vec<_>, _>>()?,
546        );
547        let results = symbols.slot_wasm_result(ret)?;
548        let abi = MethodSlotAbi {
549            params: params.iter().skip(1).copied().collect(),
550            ret: results.first().copied(),
551        };
552        let sig_idx = next_index(next_type_idx)?;
553        types.ty().function(params, results);
554        symbols.record_class_method_sig(mangled.clone(), name.clone(), sig_idx);
555        symbols.record_class_method_abi(mangled.clone(), name.clone(), abi);
556    }
557    // Inherited slots: copy the originating ancestor's sig (and its erasure
558    // shape) under this class's key.
559    for slot in &slots {
560        if symbols.class_method_sig(mangled, &slot.name).is_none() {
561            let sig = match symbols.class_method_sig(&slot.owner, &slot.name) {
562                Some(sig) => sig,
563                None => ancestor_sig(info, symbols, class, &slot.name)?.ok_or_else(|| {
564                    internal_failure("an inherited method slot's signature was not reconstructed")
565                })?,
566            };
567            symbols.record_class_method_sig(mangled.clone(), slot.name.clone(), sig);
568        }
569        if symbols.class_method_abi(mangled, &slot.name).is_none()
570            && let Some(abi) = symbols
571                .class_method_abi(&slot.owner, &slot.name)
572                .cloned()
573                .or_else(|| {
574                    class
575                        .extends
576                        .as_ref()
577                        .and_then(|p| symbols.class_method_abi(p, &slot.name).cloned())
578                })
579        {
580            symbols.record_class_method_abi(mangled.clone(), slot.name.clone(), abi);
581        }
582    }
583
584    // 2. Reserve the (vtable, struct) pair and 3. emit the rec group.
585    let vtable_idx = next_index(next_type_idx)?;
586    let struct_idx = next_index(next_type_idx)?;
587    symbols.record_class_vtable_type(mangled.clone(), vtable_idx);
588    symbols.record_class_struct_type(mangled.clone(), struct_idx);
589
590    // Parents are reconstructed first, so a missing parent type is a broken
591    // order, not a root class.
592    let (vtable_super, struct_super) = match &class.extends {
593        Some(parent) => (
594            symbols.class_vtable_type_idx(parent),
595            symbols.class_struct_type_idx(parent),
596        ),
597        None => (Some(intrinsics.class_vtable), Some(intrinsics.object_shape)),
598    };
599    let (Some(vtable_super), Some(struct_super)) = (vtable_super, struct_super) else {
600        return Err(internal_failure(
601            "an imported class's parent types were not reconstructed first",
602        ));
603    };
604    symbols.record_struct_supertype(vtable_idx, vtable_super);
605    symbols.record_struct_supertype(struct_idx, struct_super);
606
607    let mut vtable_fields: Vec<FieldType> = vec![
608        fieldtype_ref(intrinsics.to_string_fn),
609        fieldtype_ref(intrinsics.to_json_fn),
610        fieldtype_ref(intrinsics.equals_fn),
611        fieldtype_ref(intrinsics.hash_fn),
612        fieldtype_ref_null(intrinsics.class_vtable),
613        FieldType {
614            element_type: StorageType::Val(ValType::I32),
615            mutable: false,
616        },
617    ];
618    for slot in &slots {
619        let sig = symbols
620            .class_method_sig(mangled, &slot.name)
621            .ok_or_else(|| internal_failure("an imported class method slot has no signature"))?;
622        vtable_fields.push(fieldtype_ref(sig));
623    }
624    let vtable = substruct(vtable_fields, Some(vtable_super));
625
626    let struct_ty = substruct(
627        vec![
628            fieldtype_ref(vtable_idx),
629            FieldType {
630                mutable: true,
631                ..fieldtype_ref(intrinsics.field_names)
632            },
633            FieldType {
634                mutable: true,
635                ..fieldtype_ref(intrinsics.object_fields)
636            },
637            FieldType {
638                element_type: StorageType::Val(ValType::Ref(RefType::ANYREF)),
639                mutable: true,
640            },
641            FieldType {
642                element_type: StorageType::Val(ValType::I64),
643                mutable: true,
644            },
645        ],
646        Some(struct_super),
647    );
648    types.ty().rec([vtable, struct_ty]);
649
650    // Import the producer's vtable-singleton global — the class's nominal
651    // identity. `instanceof` compares against it by ref.eq, and a local
652    // subclass's vtable wires it in as the parent link. For host classes
653    // (`Error`) the producer is the Rust runtime, which `linker.define`s it.
654    imports.import(
655        host_module_for(class),
656        crate::codegen::classes::vtable_global_export_name(mangled).as_str(),
657        EntityType::Global(GlobalType {
658            val_type: ref_to(vtable_idx),
659            mutable: false,
660            shared: false,
661        }),
662    );
663    symbols.record_class_vtable_global(mangled.clone(), next_index(next_global_idx)?);
664
665    // Record field payload slots and method vtable slots (universal slots +
666    // parent link precede).
667    for (i, name) in fields.iter().enumerate() {
668        symbols.record_class_field_slot(mangled.clone(), name.clone(), wasm_u32(i)?);
669    }
670    for (name, check) in &narrowing_checks {
671        symbols.record_class_field_narrowing_check(mangled.clone(), name.clone(), check.clone());
672    }
673    for (i, slot) in slots.iter().enumerate() {
674        // Native classes keep their methods in the vtable, without the mutable
675        // closure payload emitted by the compiler for guest class instances.
676        if class.compiled_class
677            && !slot.generic
678            && !slot.name.starts_with("get ")
679            && !slot.name.starts_with("set ")
680        {
681            symbols.record_class_payload_method(mangled.clone(), slot.name.clone());
682        }
683        symbols.record_class_method_slot(
684            mangled.clone(),
685            slot.name.clone(),
686            method_vtable_slot(i)?,
687        );
688    }
689
690    symbols
691        .class_type_parameters
692        .insert(mangled.clone(), class.generics.to_vec());
693    let parent_guarded = match &class.extends {
694        Some(parent) => {
695            symbols
696                .recorded_class_guard_layout(parent)?
697                .has_instance_guards
698        }
699        None => false,
700    };
701    let named_payload_len = fields
702        .len()
703        .checked_add(slots.iter().filter(|slot| !slot.generic).count())
704        .ok_or_else(|| internal_failure("an imported class payload is too large"))?;
705    symbols.record_class_guard_layout(
706        mangled.clone(),
707        class.extends.as_ref(),
708        wasm_u32(named_payload_len)?,
709        (!class.generics.is_empty() || !narrowing_checks.is_empty() || parent_guarded)
710            && class.compiled_class,
711    )?;
712
713    // 4. Import the constructor, ctor-init, and own method bodies.
714    let ctor_params: Vec<ValType> = class
715        .constructor
716        .iter()
717        .map(|p| symbols.slot_value_type(&p.ty))
718        .collect::<Result<_, _>>()?;
719    let ctor_param_tys: Vec<crate::Type> = class.constructor.iter().map(|p| p.ty.clone()).collect();
720    symbols.record_class_ctor_abi(mangled.clone(), ctor_params.clone());
721    let class_ref = crate::Type::class_ref(
722        crate::Package(class.package.to_string()),
723        class.name.to_string(),
724        mangled.clone(),
725        Vec::new(),
726    );
727
728    // Entry constructor: `(params) -> (ref $C)`.
729    let mut entry_params = ctor_params.clone();
730    if symbols
731        .recorded_class_guard_layout(mangled)?
732        .has_instance_guards
733    {
734        entry_params.push(ref_to(intrinsics.object_fields));
735    }
736    let ctor_sig = next_index(next_type_idx)?;
737    types.ty().function(entry_params, [ref_to(struct_idx)]);
738    let ctor_mangled = crate::mangle::extend(mangled, "constructor");
739    imports.import(
740        host_module_for(class),
741        ctor_mangled.as_str(),
742        EntityType::Function(ctor_sig),
743    );
744    let ctor_idx = next_index(next_func_idx)?;
745    symbols.record_imported_fn(ctor_mangled, ctor_idx, ctor_param_tys, class_ref, false);
746    // A host constructor can't fill its own defaults, so an argument that is
747    // `undefined` takes the declared default at the call, as a host method's does.
748    let host_defaults = super::is_host_package(class.package).then(|| {
749        class
750            .constructor
751            .iter()
752            .map(|param| param.default.clone())
753            .collect::<Vec<_>>()
754    });
755    if let Some(defaults) = &host_defaults {
756        symbols.record_host_parameter_defaults(ctor_idx, defaults.clone());
757    }
758
759    // Self-first ctor-init: `((ref $Object), params...) -> ()`.
760    let mut init_params = vec![ref_to(intrinsics.object)];
761    init_params.extend(ctor_params);
762    let init_sig = next_index(next_type_idx)?;
763    types.ty().function(init_params, Vec::<ValType>::new());
764    let init_mangled = crate::mangle::extend(mangled, "constructor_init");
765    imports.import(
766        host_module_for(class),
767        init_mangled.as_str(),
768        EntityType::Function(init_sig),
769    );
770    let init_idx = next_index(next_func_idx)?;
771    symbols.record_class_ctor_init_func(mangled.clone(), init_idx);
772    if let Some(defaults) = host_defaults {
773        symbols.record_host_parameter_defaults(
774            init_idx,
775            std::iter::once(None).chain(defaults).collect(),
776        );
777    }
778
779    // Own method bodies — reuse the slot's `((ref $Object), params...) -> ret`
780    // sig. A local subclass's vtable global `ref.func`s the inherited ones.
781    for name in methods.keys() {
782        let sig = symbols
783            .class_method_sig(mangled, name)
784            .ok_or_else(|| internal_failure("an imported class's own method has no signature"))?;
785        let method_mangled = crate::mangle::extend(mangled, name);
786        imports.import(
787            host_module_for(class),
788            method_mangled.as_str(),
789            EntityType::Function(sig),
790        );
791        let idx = next_index(next_func_idx)?;
792        symbols.record_class_method_func(mangled.clone(), name.clone(), idx);
793    }
794
795    let ctor_typed_params = class
796        .constructor
797        .iter()
798        .map(|p| crate::TypedParam {
799            name: crate::Ident {
800                name: p.name.clone(),
801                span: crate::Span::at(file),
802            },
803            ty: p.ty.clone(),
804            optional: p.optional,
805            boxed: false,
806            rest: p.rest,
807            default: p.default.clone(),
808        })
809        .collect();
810
811    let mut private_members = parent_layout
812        .map(|layout| layout.private_members.clone())
813        .unwrap_or_default();
814    for (name, field) in class.fields {
815        if field.visibility == crate::Visibility::Private {
816            private_members.insert(name.clone());
817        }
818    }
819    for accessor in class.accessors {
820        if class
821            .fields
822            .get(accessor.name())
823            .is_some_and(|field| field.visibility == crate::Visibility::Private)
824        {
825            private_members.insert(super::classes::accessor_getter_name(accessor.name()));
826            private_members.insert(super::classes::accessor_setter_name(accessor.name()));
827        }
828    }
829    Ok(ImportedClassLayout {
830        private_members,
831        fields,
832        optional_fields,
833        narrowing_checks,
834        methods: slots,
835        ctor_params: ctor_typed_params,
836    })
837}
838
839/// Walk the `extends` chain to find the class that originates `method`, returning
840/// the sig recorded under it. Used only as a fallback for inherited slots.
841fn ancestor_sig(
842    info: &BTreeMap<MangledName, ClassInfo<'_>>,
843    symbols: &SymbolTable,
844    class: &ClassInfo<'_>,
845    method: &str,
846) -> Result<Option<u32>, CompilerFailure> {
847    let mut visited: BTreeSet<&MangledName> = BTreeSet::new();
848    let mut cur = class.extends.as_ref();
849    while let Some(m) = cur {
850        if !visited.insert(m) {
851            return Err(internal_failure(format!(
852                "imported class `{m}` inherits from itself"
853            )));
854        }
855        if let Some(sig) = symbols.class_method_sig(m, method) {
856            return Ok(Some(sig));
857        }
858        cur = info.get(m).and_then(|c| c.extends.as_ref());
859    }
860    Ok(None)
861}
862
863fn ref_to(idx: u32) -> ValType {
864    ValType::Ref(RefType {
865        nullable: false,
866        heap_type: HeapType::Concrete(idx),
867    })
868}
869
870fn fieldtype_ref(idx: u32) -> FieldType {
871    FieldType {
872        element_type: StorageType::Val(ref_to(idx)),
873        mutable: false,
874    }
875}
876
877fn fieldtype_ref_null(idx: u32) -> FieldType {
878    FieldType {
879        element_type: StorageType::Val(ValType::Ref(RefType {
880            nullable: true,
881            heap_type: HeapType::Concrete(idx),
882        })),
883        mutable: false,
884    }
885}
886
887fn substruct(fields: Vec<FieldType>, supertype: Option<u32>) -> SubType {
888    SubType {
889        is_final: false,
890        supertype_idx: supertype,
891        composite_type: wasm_encoder::CompositeType {
892            inner: wasm_encoder::CompositeInnerType::Struct(StructType {
893                fields: fields.into_boxed_slice(),
894            }),
895            shared: false,
896            descriptor: None,
897            describes: None,
898        },
899    }
900}
901
902#[cfg(test)]
903mod tests {
904    use super::*;
905    use crate::compile::{PackageSourceModule, compile_package_checked};
906    use crate::compiler_error::CompilerFailure;
907
908    fn declaration() -> crate::PackageDeclaration {
909        let modules = [PackageSourceModule {
910            path: crate::ModulePath::from("lib"),
911            source: "export class A { x: number = 1 } export class B extends A {}",
912        }];
913        compile_package_checked("dep", crate::ModulePath::from("lib"), &modules, &[])
914            .unwrap()
915            .declaration
916    }
917
918    /// A local class extending the imported `B`, so B and its ancestors are needed.
919    fn subclass_of(parent: &MangledName) -> TypedAst {
920        let (mut ta, diagnostics) = crate::compile::typecheck_to_typed_ast(
921            "class C {} function main(): void {}",
922            crate::FileId(0),
923        );
924        assert!(diagnostics.is_empty(), "{diagnostics:?}");
925        for decl in &mut ta.types {
926            if let TypedTypeDecl::Class(class) = decl {
927                class.extends = Some(parent.clone());
928            }
929        }
930        ta
931    }
932
933    fn mangled(declaration: &crate::PackageDeclaration, name: &str) -> MangledName {
934        declaration
935            .runtime_types
936            .values()
937            .chain(declaration.types.values())
938            .find(|symbol| symbol.name == name)
939            .unwrap()
940            .mangled_name
941            .clone()
942    }
943
944    #[test]
945    fn incomplete_or_cyclic_dependency_closures_are_internal_failures() {
946        let declaration = declaration();
947        let a = mangled(&declaration, "A");
948        let b = mangled(&declaration, "B");
949        let ta = subclass_of(&b);
950        let usage = DependencyUsage::empty();
951
952        let dependencies = [&declaration];
953        let info = collect_class_info(&dependencies);
954        assert_eq!(
955            needed_in_topo_order(&info, &ta, &usage).unwrap(),
956            [a.clone(), b.clone()]
957        );
958
959        // A missing ancestor fails rather than reconstructing a subclass
960        // without its prefix, which would link as a type mismatch.
961        let mut incomplete = declaration.clone();
962        incomplete
963            .runtime_types
964            .retain(|_, symbol| symbol.mangled_name != a);
965        incomplete
966            .types
967            .retain(|_, symbol| symbol.mangled_name != a);
968        let dependencies = [&incomplete];
969        let info = collect_class_info(&dependencies);
970        assert!(matches!(
971            needed_in_topo_order(&info, &ta, &usage),
972            Err(CompilerFailure::Internal { .. })
973        ));
974
975        let mut cyclic = declaration;
976        for symbol in cyclic
977            .runtime_types
978            .values_mut()
979            .chain(cyclic.types.values_mut())
980        {
981            if symbol.mangled_name == a
982                && let TypeKind::Class { extends, .. } = &mut symbol.kind
983            {
984                *extends = Some(crate::ClassExtends {
985                    parent: b.clone(),
986                    args: Vec::new(),
987                });
988            }
989        }
990        let dependencies = [&cyclic];
991        let info = collect_class_info(&dependencies);
992        assert!(matches!(
993            needed_in_topo_order(&info, &ta, &usage),
994            Err(CompilerFailure::Internal { .. })
995        ));
996    }
997}