Skip to main content

submilli_engine/codegen/
cast_check.rs

1//! The structural conformance test and its consumers: `x as T`, the `!`
2//! non-null assertion, and the read guard on a narrowed field redeclaration.
3
4use wasm_encoder::{BlockType, Function, HeapType, Instruction, RefType, ValType};
5
6use crate::codegen::function_emitter::expr::emit_expr;
7use crate::codegen::function_emitter::json::emit_inline_const_raw_string;
8use crate::codegen::function_emitter::{FunctionEmitter, cast};
9use crate::codegen::{CodegenCtx, cast_diagnostics as diagnostic};
10use crate::{ExprId, Ident, Span, Type};
11
12pub const TYPE_TAG_STRINGS: &[&str] = &[
13    "string",
14    "number",
15    "boolean",
16    "function",
17    "object",
18    "null",
19    "undefined",
20];
21pub(super) const RECURSIVE_VALIDATOR_CAPACITY: i32 = 1024;
22
23#[derive(Clone, Copy)]
24struct ValidatorState {
25    visited: u32,
26    validator_ids: u32,
27    depth: u32,
28}
29
30#[derive(Clone, Copy)]
31struct FieldConformanceLocals {
32    object: u32,
33    field: u32,
34}
35
36#[derive(Clone, Copy)]
37struct CollectionStorage {
38    backing: u32,
39    backing_type: u32,
40    order_len_field: u32,
41    order: u32,
42}
43
44pub fn error_prefix(target_ty: &Type) -> String {
45    format!("type mismatch: expected {target_ty}, got ")
46}
47
48/// `value as target_ty`. `check` is `Some(shape)` when a runtime structural check is needed
49/// (the source isn't a static subtype of the target — `unknown`/downcasts); `None` for a
50/// statically-proven upcast (repr-only narrow). `shape` has interfaces reduced to their
51/// object shapes so this walks plain structural types.
52/// `cast_span` is the cast expression's own span, mapped while its runtime
53/// check is emitted so a limit the check reaches, and a failed check, are
54/// reported at the cast rather than at the last part of the value. The code
55/// after the cast maps to the enclosing span again.
56pub fn emit_cast(
57    emitter: &mut FunctionEmitter,
58    ctx: &CodegenCtx,
59    value: ExprId,
60    target_ty: &Type,
61    check: Option<&Type>,
62    cast_span: crate::Span,
63) -> Result<(), crate::compiler_error::CompilerFailure> {
64    let source_ty = ctx
65        .ta
66        .try_expr(value)
67        .map_err(crate::codegen::arena_failure)?
68        .ty
69        .clone();
70
71    // unknown accepts every value — box to $Object, no test.
72    if matches!(target_ty.peel(), Type::Unknown) {
73        emit_expr(emitter, ctx, value)?;
74        cast::emit_box(emitter, ctx, &source_ty)?;
75        return Ok(());
76    }
77
78    let Some(check_shape) = check else {
79        let static_ty = ctx
80            .ta
81            .source_type(value)
82            .map_err(crate::codegen::arena_failure)?;
83        if crate::codegen::closure_coercions::is_union_call_cast(static_ty, target_ty) {
84            emit_expr(emitter, ctx, value)?;
85            cast::emit_box(emitter, ctx, &source_ty)?;
86            return crate::codegen::closure_coercions::emit_union_call_cast(
87                emitter, ctx, target_ty,
88            );
89        }
90        if &source_ty != static_ty {
91            emit_expr(emitter, ctx, value)?;
92            let enclosing = map_cast_span(emitter, cast_span);
93            emit_checked_cast_on_stack(emitter, ctx, &source_ty, target_ty)?;
94            restore_span(emitter, enclosing);
95            return Ok(());
96        }
97        // Statically-proven upcast: box then narrow representation, no runtime test.
98        emit_expr(emitter, ctx, value)?;
99        cast::emit_box(emitter, ctx, &source_ty)?;
100        cast::emit_cast_to(emitter, ctx, target_ty)?;
101        return Ok(());
102    };
103
104    let mark = emitter.single_evaluation_mark();
105    emit_expr(emitter, ctx, value)?;
106    emitter.end_single_evaluations(mark)?;
107    let enclosing = map_cast_span(emitter, cast_span);
108    cast::emit_box(emitter, ctx, &source_ty)?;
109    emit_shape_cast_on_stack(emitter, ctx, target_ty, check_shape)?;
110    restore_span(emitter, enclosing);
111    Ok(())
112}
113
114/// The runtime half of `value as target_ty` for the boxed value on the stack:
115/// test it against `check_shape` and narrow it, or throw the cast's
116/// `TypeError`.
117pub(crate) fn emit_shape_cast_on_stack(
118    emitter: &mut FunctionEmitter,
119    ctx: &CodegenCtx,
120    target_ty: &Type,
121    check_shape: &Type,
122) -> Result<(), crate::compiler_error::CompilerFailure> {
123    let object_idx = ctx
124        .symbols
125        .intrinsic_type_indices()
126        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?
127        .object;
128    let scratch_ty = ValType::Ref(RefType {
129        nullable: true,
130        heap_type: HeapType::Concrete(object_idx),
131    });
132    let scratch = emitter.add_anonymous_local(scratch_ty)?;
133    emitter.instruction(Instruction::LocalSet(scratch));
134    let previous_diagnostic = diagnostic::begin(emitter, ctx);
135
136    emit_structural_test(emitter, ctx, scratch, check_shape, target_ty)?;
137
138    let target_val = ctx.symbols.value_type(target_ty)?;
139    let block_ty = BlockType::Result(target_val);
140    emitter.emit_if(block_ty);
141    emitter.instruction(Instruction::LocalGet(scratch));
142    cast::emit_cast_to(emitter, ctx, target_ty)?;
143    emitter.emit_else();
144    emit_cast_throw(emitter, ctx, scratch, target_ty);
145    emitter.emit_end();
146    emitter.cast_diagnostic = previous_diagnostic;
147    Ok(())
148}
149
150/// Maps `cast_span` to the code that follows, returning the span it replaces.
151fn map_cast_span(emitter: &mut FunctionEmitter, cast_span: crate::Span) -> Option<crate::Span> {
152    let enclosing = emitter.last_mapped_span();
153    emitter.record_span(cast_span);
154    enclosing
155}
156
157fn restore_span(emitter: &mut FunctionEmitter, enclosing: Option<crate::Span>) {
158    if let Some(span) = enclosing {
159        emitter.record_span(span);
160    }
161}
162
163/// `value!`. Converts a runtime `null` into a catchable `TypeError` instead
164/// of relying on `ref.as_non_null`, which would trap.
165pub fn emit_non_null_assert(
166    emitter: &mut FunctionEmitter,
167    ctx: &CodegenCtx,
168    value: ExprId,
169    target_ty: &Type,
170) -> Result<(), crate::compiler_error::CompilerFailure> {
171    let source_ty = ctx
172        .ta
173        .try_expr(value)
174        .map_err(crate::codegen::arena_failure)?
175        .ty
176        .clone();
177    emit_expr(emitter, ctx, value)?;
178    emit_non_null_assert_on_stack(emitter, ctx, &source_ty, target_ty)?;
179    Ok(())
180}
181
182/// `!` applied to a value already on the stack at `source_ty`'s slot — the
183/// optional-chain form, where the operand is the step before rather than an
184/// expression this can emit itself.
185pub fn emit_non_null_assert_on_stack(
186    emitter: &mut FunctionEmitter,
187    ctx: &CodegenCtx,
188    source_ty: &Type,
189    target_ty: &Type,
190) -> Result<(), crate::compiler_error::CompilerFailure> {
191    cast::emit_box(emitter, ctx, source_ty)?;
192
193    let object_idx = object_idx_of(ctx)?;
194    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx))?;
195    emitter.instruction(Instruction::LocalSet(scratch));
196
197    emitter.instruction(Instruction::LocalGet(scratch));
198    crate::codegen::function_emitter::expr::emit_is_nullish(emitter, ctx)?;
199    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
200    crate::codegen::throw::emit_type_error_throw(
201        emitter,
202        ctx,
203        crate::codegen::throw::NON_NULL_ASSERT_MESSAGE,
204    );
205    emitter.emit_else();
206    emitter.instruction(Instruction::LocalGet(scratch));
207    cast::emit_cast_to(emitter, ctx, target_ty)?;
208    emitter.emit_end();
209
210    Ok(())
211}
212
213/// Pushes i32 1 if the `(ref null $Object)` in `value_local` structurally conforms to
214/// `ty`, 0 otherwise — no throw, so it composes inside unions/fields/elements. Recurses
215/// into object fields, array elements, tuple slots, and union members; verifies number and
216/// string literal values.
217///
218/// `typed_ast::runtime_type_is_testable` is the shared allowlist deciding which
219/// types may reach this function. `codegen::analysis::note_shape_member_names`
220/// separately registers the per-name globals an arm's field scan reads. A new
221/// arm must update that registration when it reads names, and may enter the
222/// allowlist only if every value the type admits is one this test accepts.
223///
224/// `named` is the type a limit error names: the type the program wrote, where
225/// `ty` is the shape it was reduced to.
226pub(super) fn emit_structural_test(
227    emitter: &mut FunctionEmitter,
228    ctx: &CodegenCtx,
229    value_local: u32,
230    ty: &Type,
231    named: &Type,
232) -> Result<(), crate::compiler_error::CompilerFailure> {
233    ctx.checking(named, || {
234        emit_structural_test_inner(
235            emitter,
236            ctx,
237            value_local,
238            ty,
239            &mut std::collections::BTreeSet::new(),
240            None,
241        )?;
242        // A step's own code follows its charge, so the finished check is
243        // measured again before the function can reach the engine.
244        ctx.check_function_limits(emitter)
245    })
246}
247
248fn emit_structural_test_inner(
249    emitter: &mut FunctionEmitter,
250    ctx: &CodegenCtx,
251    value_local: u32,
252    ty: &Type,
253    interface_stack: &mut std::collections::BTreeSet<Type>,
254    validator_state: Option<ValidatorState>,
255) -> Result<(), crate::compiler_error::CompilerFailure> {
256    ctx.charge_validator_step(emitter)?;
257    let checkpoint = diagnostic::checkpoint(emitter, ctx);
258    let i32_block = BlockType::Result(ValType::I32);
259    match ty.lowered() {
260        Type::Union(members) => {
261            let union = diagnostic::union_start(emitter, ctx, members);
262            let mut first = true;
263            for m in members {
264                diagnostic::union_next(emitter, ctx);
265                emit_structural_test_inner(
266                    emitter,
267                    ctx,
268                    value_local,
269                    m,
270                    interface_stack,
271                    validator_state,
272                )?;
273                diagnostic::union_keep(emitter, ctx, union);
274                if first {
275                    first = false;
276                } else {
277                    emitter.instruction(Instruction::I32Or);
278                }
279            }
280            if first {
281                emitter.instruction(Instruction::I32Const(0));
282            }
283            diagnostic::union_end(emitter, ctx, union)?;
284        }
285        Type::Null => {
286            emitter.instruction(Instruction::LocalGet(value_local));
287            emitter.instruction(Instruction::RefIsNull);
288        }
289        Type::Undefined | Type::Void => {
290            emitter.instruction(Instruction::LocalGet(value_local));
291            crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
292        }
293        // `{}` holds every value but `null` and `undefined`.
294        Type::Unknown if ty.is_empty_object() => {
295            emitter.instruction(Instruction::LocalGet(value_local));
296            emitter.instruction(Instruction::RefIsNull);
297            emitter.instruction(Instruction::LocalGet(value_local));
298            crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
299            emitter.instruction(Instruction::I32Or);
300            emitter.instruction(Instruction::I32Eqz);
301        }
302        // `object` holds every value but the primitives, `null` and `undefined`.
303        Type::Unknown if matches!(ty.peel(), Type::NonPrimitive) => {
304            crate::codegen::function_emitter::expr::emit_is_non_primitive(
305                emitter,
306                ctx,
307                value_local,
308                crate::codegen::function_emitter::expr::Closures::AreObjects,
309            )?;
310        }
311        Type::Unknown => emitter.instruction(Instruction::I32Const(1)),
312        Type::Number => emit_ref_test(emitter, value_local, boxed_number_idx(ctx)?),
313        Type::Boolean => emit_ref_test(emitter, value_local, boxed_boolean_idx(ctx)?),
314        Type::String => emit_ref_test(emitter, value_local, string_idx(ctx)?),
315        Type::NumberEnum { .. } | Type::StringEnum { .. } => {
316            if let Some(crate::FieldNarrowingTest::Shape(members)) =
317                ctx.ta.runtime_type_tests.get(ty.lowered())
318                && members.lowered() != ty.lowered()
319            {
320                emit_structural_test_inner(
321                    emitter,
322                    ctx,
323                    value_local,
324                    members,
325                    interface_stack,
326                    validator_state,
327                )?;
328            } else {
329                emit_representation_test(emitter, ctx, value_local, ty)?;
330            }
331        }
332        Type::BigInt => emit_ref_test(
333            emitter,
334            value_local,
335            ctx.symbols
336                .bigint_type_idx()
337                .ok_or_else(|| crate::codegen::internal_failure("$BigInt intrinsic registered"))?,
338        ),
339        Type::Uint8Array => emit_ref_test(
340            emitter,
341            value_local,
342            ctx.symbols.uint8_array_type_idx().ok_or_else(|| {
343                crate::codegen::internal_failure("$Uint8Array intrinsic registered")
344            })?,
345        ),
346        Type::Function { has_rest, .. } => {
347            emit_function_structural_test(emitter, ctx, value_local, ty, *has_rest)?;
348        }
349        // Generic leaves use the caller's concrete predicate when available.
350        // Legacy entry points without descriptors retain their erased checks.
351        Type::TypeVar(name) | Type::GenericParam { name, .. } => {
352            super::runtime_descriptors::test_parameter(emitter, ctx, name, value_local)?;
353        }
354        Type::NumberLiteral(n) => {
355            let boxed = boxed_number_idx(ctx)?;
356            emitter.instruction(Instruction::LocalGet(value_local));
357            emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(boxed)));
358            emitter.emit_if(i32_block);
359            emitter.instruction(Instruction::LocalGet(value_local));
360            emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(boxed)));
361            emitter.instruction(Instruction::StructGet {
362                struct_type_index: boxed,
363                field_index: 1,
364            });
365            emitter.instruction(Instruction::F64Const(n.0.into()));
366            emitter.instruction(Instruction::F64Eq);
367            emitter.emit_else();
368            emitter.instruction(Instruction::I32Const(0));
369            emitter.emit_end();
370        }
371        Type::BooleanLiteral(value) => {
372            let boxed = boxed_boolean_idx(ctx)?;
373            emitter.instruction(Instruction::LocalGet(value_local));
374            emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(boxed)));
375            emitter.emit_if(i32_block);
376            emitter.instruction(Instruction::LocalGet(value_local));
377            emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(boxed)));
378            emitter.instruction(Instruction::StructGet {
379                struct_type_index: boxed,
380                field_index: 1,
381            });
382            emitter.instruction(Instruction::I32Const(i32::from(*value)));
383            emitter.instruction(Instruction::I32Eq);
384            emitter.emit_else();
385            emitter.instruction(Instruction::I32Const(0));
386            emitter.emit_end();
387        }
388        Type::TemplateLiteral { .. } => {
389            let string = string_idx(ctx)?;
390            emitter.instruction(Instruction::LocalGet(value_local));
391            emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(string)));
392            emitter.emit_if(i32_block);
393            emitter.instruction(Instruction::LocalGet(value_local));
394            let domain = serde_json::to_string(&crate::property_keys::runtime_domain(ty).map_err(
395                |error| error.into_failure(crate::compiler_error::CompilerStage::Codegen, None),
396            )?)
397            .map_err(|error| {
398                crate::codegen::internal_failure(format!("could not encode template type: {error}"))
399            })?;
400            emit_inline_string_checked(emitter, ctx, &domain)?;
401            let helper = ctx
402                .symbols
403                .prelude_func_idx("ObjectConstructor##matchesKeyDomain")
404                .ok_or_else(|| crate::codegen::internal_failure("template matcher imported"))?;
405            emitter.instruction(Instruction::Call(helper));
406            emitter.emit_else();
407            emitter.instruction(Instruction::I32Const(0));
408            emitter.emit_end();
409        }
410        Type::StringLiteral(s) => {
411            let str_idx = string_idx(ctx)?;
412            let intr = ctx
413                .symbols
414                .intrinsic_type_indices()
415                .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
416            emitter.instruction(Instruction::LocalGet(value_local));
417            emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(str_idx)));
418            emitter.emit_if(i32_block);
419            emitter.instruction(Instruction::LocalGet(value_local));
420            emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(str_idx)));
421            emitter.instruction(Instruction::StructGet {
422                struct_type_index: str_idx,
423                field_index: 1,
424            });
425            let raw_local = emitter.add_anonymous_local(ValType::Ref(RefType {
426                nullable: false,
427                heap_type: HeapType::Concrete(intr.raw_string),
428            }))?;
429            emitter.instruction(Instruction::LocalSet(raw_local));
430            crate::codegen::function_emitter::json::emit_raw_string_matches_literal(
431                emitter, intr, raw_local, s,
432            );
433            emitter.emit_else();
434            emitter.instruction(Instruction::I32Const(0));
435            emitter.emit_end();
436        }
437        Type::Object { fields, index } => {
438            let shape_idx = ctx.symbols.object_shape_type_idx().ok_or_else(|| {
439                crate::codegen::internal_failure("$ObjectShape intrinsic registered")
440            })?;
441            emitter.instruction(Instruction::LocalGet(value_local));
442            emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(shape_idx)));
443            emitter.emit_if(i32_block);
444            let obj_local = emitter.add_anonymous_local(ValType::Ref(RefType {
445                nullable: false,
446                heap_type: HeapType::Concrete(shape_idx),
447            }))?;
448            emitter.instruction(Instruction::LocalGet(value_local));
449            emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(shape_idx)));
450            emitter.instruction(Instruction::LocalSet(obj_local));
451            let field_local =
452                emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
453            emitter.instruction(Instruction::I32Const(1)); // accumulator
454            for ordered in diagnostic::check_order(emitter, fields) {
455                emit_field_conformance(
456                    emitter,
457                    ctx,
458                    FieldConformanceLocals {
459                        object: obj_local,
460                        field: field_local,
461                    },
462                    ordered,
463                    interface_stack,
464                    validator_state,
465                )?;
466                emitter.instruction(Instruction::I32And);
467            }
468            if let Some(index) = index {
469                emit_index_conformance(
470                    emitter,
471                    ctx,
472                    obj_local,
473                    index,
474                    interface_stack,
475                    validator_state,
476                )?;
477                emitter.instruction(Instruction::I32And);
478            }
479            emitter.emit_else();
480            emitter.instruction(Instruction::I32Const(0));
481            emitter.emit_end();
482        }
483        Type::Array(elem) => {
484            emit_elements_test(
485                emitter,
486                ctx,
487                value_local,
488                elem,
489                interface_stack,
490                validator_state,
491                None,
492            )?;
493        }
494        Type::Tuple(elems) => {
495            let intr = ctx
496                .symbols
497                .intrinsic_type_indices()
498                .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
499            let array_idx = ctx
500                .symbols
501                .array_type_idx()
502                .ok_or_else(|| crate::codegen::internal_failure("$Array intrinsic registered"))?;
503            emitter.instruction(Instruction::LocalGet(value_local));
504            emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(array_idx)));
505            emitter.emit_if(i32_block);
506            let raw_local = emitter.add_anonymous_local(ValType::Ref(RefType {
507                nullable: false,
508                heap_type: HeapType::Concrete(intr.raw_array),
509            }))?;
510            emitter.instruction(Instruction::LocalGet(value_local));
511            emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(array_idx)));
512            emitter.instruction(Instruction::StructGet {
513                struct_type_index: array_idx,
514                field_index: 1,
515            });
516            emitter.instruction(Instruction::LocalSet(raw_local));
517            // Validate the logical length before reading any tuple position.
518            let length = emitter.add_anonymous_local(ValType::I32)?;
519            emitter.instruction(Instruction::LocalGet(value_local));
520            emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(array_idx)));
521            emitter.instruction(Instruction::StructGet {
522                struct_type_index: array_idx,
523                field_index: 2,
524            });
525            emitter.instruction(Instruction::LocalTee(length));
526            emitter.instruction(Instruction::I32Const(
527                crate::codegen::wasm_u32(elems.len())?.cast_signed(),
528            ));
529            emitter.instruction(Instruction::I32LeU);
530            emitter.instruction(Instruction::LocalGet(length));
531            emitter.instruction(Instruction::I32Const(
532                crate::codegen::wasm_u32(elems.required_len())?.cast_signed(),
533            ));
534            emitter.instruction(Instruction::I32GeU);
535            emitter.instruction(Instruction::I32And);
536            emitter.emit_if(i32_block);
537            let elem_local = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
538            emitter.instruction(Instruction::I32Const(1));
539            for (idx, et) in elems.iter().enumerate() {
540                if idx >= elems.required_len() {
541                    emitter.instruction(Instruction::LocalGet(length));
542                    emitter.instruction(Instruction::I32Const(
543                        crate::codegen::wasm_u32(idx)?.cast_signed(),
544                    ));
545                    emitter.instruction(Instruction::I32LeU);
546                    emitter.emit_if(i32_block);
547                    emitter.instruction(Instruction::I32Const(1));
548                    emitter.emit_else();
549                }
550                let index = emitter.add_anonymous_local(ValType::I32)?;
551                emitter.instruction(Instruction::I32Const(
552                    crate::codegen::wasm_u32(idx)?.cast_signed(),
553                ));
554                emitter.instruction(Instruction::LocalSet(index));
555                let parent_path = diagnostic::index(emitter, ctx, index);
556                emitter.instruction(Instruction::LocalGet(raw_local));
557                emitter.instruction(Instruction::I32Const(
558                    crate::codegen::wasm_u32(idx)?.cast_signed(),
559                ));
560                emitter.instruction(Instruction::ArrayGet(intr.raw_array));
561                emitter.instruction(Instruction::LocalSet(elem_local));
562                emit_structural_test_inner(
563                    emitter,
564                    ctx,
565                    elem_local,
566                    et,
567                    interface_stack,
568                    validator_state,
569                )?;
570                diagnostic::pop(emitter, parent_path);
571                if idx >= elems.required_len() {
572                    emitter.emit_end();
573                }
574                emitter.instruction(Instruction::I32And);
575            }
576            emitter.emit_else();
577            emitter.instruction(Instruction::I32Const(0));
578            emitter.emit_end();
579            emitter.emit_else();
580            emitter.instruction(Instruction::I32Const(0));
581            emitter.emit_end();
582        }
583        // `keyof T`, `T[K]` and a utility over `T` are erased like `T`, with no
584        // descriptor of their own to test against (casts to them are rejected
585        // at typecheck, SUB-2152), so a read guard over one admits the value.
586        // So does one codegen instantiated (`Partial<{ a: number }>` for a
587        // generic class's field): it has no type table to evaluate it with.
588        operator
589            if operator.is_utility_application()
590                || matches!(operator, Type::KeyOf(_) | Type::IndexedAccess { .. }) =>
591        {
592            emitter.instruction(Instruction::I32Const(1));
593        }
594        // Recursion back-edge: delegate to the alias's generated validator, which
595        // re-enters this test on the alias body. Terminates on the finite value.
596        Type::AliasRef { .. } => {
597            let func_idx = ctx
598                .symbols
599                .runtime_validator_idx(ty.lowered())
600                .ok_or_else(|| {
601                    crate::codegen::internal_failure(
602                        "recursive runtime validator pre-allocated during discovery",
603                    )
604                })?;
605            emitter.instruction(Instruction::LocalGet(value_local));
606            let visited = emit_validator_state(emitter, ctx, validator_state)?;
607            super::runtime_descriptors::validator_environment(emitter, ctx, ty)?;
608            emitter.instruction(Instruction::Call(func_idx));
609            diagnostic::load_recursive(emitter, ctx, visited);
610        }
611        Type::InterfaceRef { .. } => emit_interface_ref_test(
612            emitter,
613            ctx,
614            value_local,
615            ty,
616            interface_stack,
617            validator_state,
618        )?,
619        // Nominal, not structural: same-shape sibling classes canonicalize to one
620        // WasmGC type, so membership is the vtable-parent walk `instanceof` uses.
621        // Unreachable from `as` (class targets are rejected at typecheck); reached
622        // from the narrowed-field read guard.
623        Type::ClassRef { mangled, .. } => {
624            let vtable_global = ctx
625                .symbols
626                .class_vtable_global_idx(mangled)
627                .ok_or_else(|| crate::codegen::internal_failure("class vtable global recorded"))?;
628            emitter.instruction(Instruction::LocalGet(value_local));
629            cast::emit_nominal_instance_test(emitter, ctx, vtable_global)?;
630            if let Some(validator) = ctx.symbols.runtime_validator_idx(ty.lowered()) {
631                emitter.emit_if(BlockType::Result(ValType::I32));
632                emitter.instruction(Instruction::LocalGet(value_local));
633                let visited = emit_validator_state(emitter, ctx, validator_state)?;
634                super::runtime_descriptors::validator_environment(emitter, ctx, ty)?;
635                emitter.instruction(Instruction::Call(validator));
636                diagnostic::load_recursive(emitter, ctx, visited);
637                emitter.emit_else();
638                emitter.instruction(Instruction::I32Const(0));
639                emitter.emit_end();
640            }
641        }
642        // Rejected at typecheck (`unsupported_cast_target_reason`); defensive 0.
643        _ => emitter.instruction(Instruction::I32Const(0)),
644    }
645    diagnostic::finish(
646        emitter,
647        ctx,
648        checkpoint,
649        diagnostic::Tested::Value {
650            ty,
651            local: value_local,
652        },
653    )?;
654    Ok(())
655}
656
657/// Pushes whether the value in `value_local` is a closure that can stand for
658/// the function type `ty`. A rest closure shares the struct of its Wasm arity
659/// but takes its arguments packed, and only a closure whose environment carries
660/// argument metadata can have one. So a closure of the target's struct without
661/// metadata passes outright unless the target ends in a rest parameter, and
662/// `__value_defaults_fit` decides every other case.
663fn emit_function_structural_test(
664    emitter: &mut FunctionEmitter,
665    ctx: &CodegenCtx,
666    value_local: u32,
667    ty: &Type,
668    has_rest: bool,
669) -> Result<(), crate::compiler_error::CompilerFailure> {
670    let signature = crate::codegen::closures::classify(ty)?;
671    let closure_struct = ctx
672        .symbols
673        .closure_struct_type_idx(signature)
674        .ok_or_else(|| {
675            crate::codegen::internal_failure("closure signature registered during analysis")
676        })?;
677    let target = super::closure_coercions::DefaultsFitTarget::exact(signature, has_rest);
678    // A `void` function fits a function type returning `unknown`, as in
679    // TypeScript; the cast adapts its convention.
680    let returns_unknown = matches!(ty.lowered(), Type::Function { ret, .. } if matches!(ret.lowered(), Type::Unknown));
681    let target = if returns_unknown {
682        target.either_convention()
683    } else {
684        target
685    };
686    let fit = |emitter: &mut FunctionEmitter| {
687        super::closure_coercions::emit_defaults_fit(emitter, ctx, value_local, target)
688    };
689    emit_ref_test(emitter, value_local, closure_struct);
690    emitter.emit_if(BlockType::Result(ValType::I32));
691    super::closure_coercions::emit_may_have_argument_metadata(
692        emitter,
693        ctx,
694        value_local,
695        closure_struct,
696    )?;
697    emitter.emit_if(BlockType::Result(ValType::I32));
698    fit(emitter)?;
699    emitter.emit_else();
700    emitter.instruction(Instruction::I32Const(i32::from(!has_rest)));
701    emitter.emit_end();
702    emitter.emit_else();
703    if has_rest {
704        emitter.instruction(Instruction::I32Const(0));
705    } else {
706        fit(emitter)?;
707    }
708    emitter.emit_end();
709    Ok(())
710}
711
712fn emit_interface_ref_test(
713    emitter: &mut FunctionEmitter,
714    ctx: &CodegenCtx,
715    value_local: u32,
716    ty: &Type,
717    interface_stack: &mut std::collections::BTreeSet<Type>,
718    validator_state: Option<ValidatorState>,
719) -> Result<(), crate::compiler_error::CompilerFailure> {
720    let key = ty.lowered().clone();
721    if ctx.symbols.generic_runtime_validator(&key).is_some() {
722        emit_validator_or_representation(emitter, ctx, value_local, ty, &key, validator_state)?;
723        return Ok(());
724    }
725    let _: () = match ctx.ta.runtime_type_tests.get(&key) {
726        Some(crate::FieldNarrowingTest::Shape(shape)) => emit_structural_test_inner(
727            emitter,
728            ctx,
729            value_local,
730            shape,
731            interface_stack,
732            validator_state,
733        )?,
734        Some(crate::FieldNarrowingTest::Interface(test)) if interface_stack.insert(key.clone()) => {
735            emit_interface_test_inner(
736                emitter,
737                ctx,
738                value_local,
739                ty,
740                test,
741                interface_stack,
742                validator_state,
743            )?;
744            interface_stack.remove(&key);
745        }
746        _ => {
747            emit_validator_or_representation(emitter, ctx, value_local, ty, &key, validator_state)?;
748        }
749    };
750    Ok(())
751}
752
753fn emit_validator_or_representation(
754    emitter: &mut FunctionEmitter,
755    ctx: &CodegenCtx,
756    value_local: u32,
757    ty: &Type,
758    key: &Type,
759    validator_state: Option<ValidatorState>,
760) -> Result<(), crate::compiler_error::CompilerFailure> {
761    if let Some(func_idx) = ctx.symbols.runtime_validator_idx(key) {
762        emitter.instruction(Instruction::LocalGet(value_local));
763        let visited = emit_validator_state(emitter, ctx, validator_state)?;
764        super::runtime_descriptors::validator_environment(emitter, ctx, key)?;
765        emitter.instruction(Instruction::Call(func_idx));
766        diagnostic::load_recursive(emitter, ctx, visited);
767    } else {
768        emit_representation_test(emitter, ctx, value_local, ty)?;
769    };
770    Ok(())
771}
772
773/// Validate an erased value against its declared target before casting it.
774/// Mismatches become a catchable `TypeError` instead of an unchecked Wasm trap.
775pub(crate) fn emit_checked_cast_on_stack(
776    emitter: &mut FunctionEmitter,
777    ctx: &CodegenCtx,
778    source_ty: &Type,
779    target_ty: &Type,
780) -> Result<(), crate::compiler_error::CompilerFailure> {
781    cast::emit_box(emitter, ctx, source_ty)?;
782    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
783    emitter.instruction(Instruction::LocalSet(scratch));
784    let previous_diagnostic = emitter.cast_diagnostic.take();
785    if diagnostic::has_nested_paths(target_ty) {
786        diagnostic::begin(emitter, ctx);
787    }
788    match ctx.ta.runtime_type_tests.get(target_ty.lowered()) {
789        Some(crate::FieldNarrowingTest::Interface(test)) => {
790            emit_interface_test(emitter, ctx, scratch, target_ty, test)?;
791        }
792        Some(crate::FieldNarrowingTest::Shape(shape)) => {
793            emit_structural_test(emitter, ctx, scratch, shape, target_ty)?;
794        }
795        // A function that declares fewer parameters than the target, or
796        // trailing defaults, still fits when its adapter can stand for the
797        // target, which the cast then builds.
798        _ if matches!(target_ty.lowered(), Type::Function { .. }) => {
799            emit_structural_test(emitter, ctx, scratch, target_ty, target_ty)?;
800        }
801        _ => emit_target_test(emitter, ctx, scratch, target_ty)?,
802    }
803    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
804    emitter.instruction(Instruction::LocalGet(scratch));
805    cast::emit_cast_to(emitter, ctx, target_ty)?;
806    emitter.emit_else();
807    emit_cast_throw(emitter, ctx, scratch, target_ty);
808    emitter.emit_end();
809    emitter.cast_diagnostic = previous_diagnostic;
810    Ok(())
811}
812
813/// Validate an erased parameter before rebinding it to the declaration's
814/// physical slot. Interface values may have host-owned carriers that cannot be
815/// reconstructed structurally here (for example Temporal plain-date structs),
816/// so parameters require representation safety; concrete value types retain
817/// the stronger structural check.
818pub(crate) fn emit_checked_parameter_cast_on_stack(
819    emitter: &mut FunctionEmitter,
820    ctx: &CodegenCtx,
821    source_ty: &Type,
822    target_ty: &Type,
823) -> Result<(), crate::compiler_error::CompilerFailure> {
824    if !is_interface_parameter(target_ty) {
825        emit_checked_cast_on_stack(emitter, ctx, source_ty, target_ty)?;
826        return Ok(());
827    }
828
829    cast::emit_box(emitter, ctx, source_ty)?;
830    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
831    emitter.instruction(Instruction::LocalSet(scratch));
832    emit_representation_test(emitter, ctx, scratch, target_ty)?;
833    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
834    emitter.instruction(Instruction::LocalGet(scratch));
835    cast::emit_cast_to(emitter, ctx, target_ty)?;
836    emitter.emit_else();
837    emit_cast_throw(emitter, ctx, scratch, target_ty);
838    emitter.emit_end();
839    Ok(())
840}
841
842fn is_interface_parameter(ty: &Type) -> bool {
843    match ty.lowered() {
844        Type::InterfaceRef { .. } => true,
845        Type::Union(members) => {
846            let mut non_null = members
847                .iter()
848                .filter(|member| !matches!(member.lowered(), Type::Null | Type::Undefined));
849            non_null
850                .next()
851                .is_some_and(|member| matches!(member.lowered(), Type::InterfaceRef { .. }))
852                && non_null.next().is_none()
853        }
854        _ => false,
855    }
856}
857
858/// Require the carrier an operation consumes, without validating its contents
859/// or invoking getters. Reading a value itself does not perform this check.
860pub(crate) fn emit_operation_cast_on_stack(
861    emitter: &mut FunctionEmitter,
862    ctx: &CodegenCtx,
863    source_ty: &Type,
864    target_ty: &Type,
865) -> Result<(), crate::compiler_error::CompilerFailure> {
866    cast::emit_box(emitter, ctx, source_ty)?;
867    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
868    emitter.instruction(Instruction::LocalSet(scratch));
869    // Operation checks validate only the carrier, so they have no nested path.
870    let previous_diagnostic = emitter.cast_diagnostic.take();
871    if matches!(target_ty.lowered(), Type::Function { .. }) {
872        let signature = crate::codegen::closures::classify(target_ty.lowered())?;
873        emit_representation_test(emitter, ctx, scratch, target_ty)?;
874        let opposite = crate::codegen::closures::ClosureSig {
875            is_void: !signature.is_void,
876            ..signature
877        };
878        if let Some(structure) = ctx.symbols.closure_struct_type_idx(opposite) {
879            emit_ref_test(emitter, scratch, structure);
880            emitter.instruction(Instruction::I32Or);
881        }
882        if matches!(
883            target_ty.lowered(),
884            Type::Function {
885                has_rest: false,
886                ..
887            }
888        ) {
889            emitter.emit_if(BlockType::Result(ValType::I32));
890            emitter.instruction(Instruction::I32Const(1));
891            emitter.emit_else();
892            super::closure_coercions::emit_defaults_fit(
893                emitter,
894                ctx,
895                scratch,
896                super::closure_coercions::DefaultsFitTarget::any_convention(signature.arity),
897            )?;
898            emitter.emit_end();
899        }
900    } else {
901        emit_target_test(emitter, ctx, scratch, target_ty)?;
902    }
903    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
904    emitter.instruction(Instruction::LocalGet(scratch));
905    cast::emit_cast_to(emitter, ctx, target_ty)?;
906    emitter.emit_else();
907    emit_cast_throw(emitter, ctx, scratch, target_ty);
908    emitter.emit_end();
909    emitter.cast_diagnostic = previous_diagnostic;
910    Ok(())
911}
912
913/// Pushes whether `value_local` holds a `target_ty`, for a target without a
914/// recorded runtime test. A primitive slot (`1 | 2` is an `f64`) has no
915/// reference shape to test, so its values are told apart structurally.
916fn emit_target_test(
917    emitter: &mut FunctionEmitter,
918    ctx: &CodegenCtx,
919    value_local: u32,
920    target_ty: &Type,
921) -> Result<(), crate::compiler_error::CompilerFailure> {
922    if matches!(
923        ctx.symbols.value_type(target_ty)?,
924        ValType::F64 | ValType::I32
925    ) {
926        emit_structural_test(emitter, ctx, value_local, target_ty, target_ty)
927    } else {
928        emit_representation_test(emitter, ctx, value_local, target_ty)
929    }
930}
931
932/// The conservative fallback for types without a full structural validator.
933/// It cannot distinguish two interface values sharing `$Object`, but it proves
934/// the following representation cast safe and turns representation mismatches
935/// into a catchable `TypeError` rather than a Wasm trap.
936fn emit_representation_test(
937    emitter: &mut FunctionEmitter,
938    ctx: &CodegenCtx,
939    value_local: u32,
940    target_ty: &Type,
941) -> Result<(), crate::compiler_error::CompilerFailure> {
942    let ValType::Ref(target) = ctx.symbols.value_type(target_ty)? else {
943        return Err(crate::codegen::internal_failure(
944            "representation test requires a reference type",
945        ));
946    };
947    let allows_null = cast::target_allows_null(ctx, target_ty)?;
948    if allows_null {
949        emitter.instruction(Instruction::LocalGet(value_local));
950        emitter.instruction(Instruction::RefIsNull);
951        emitter.emit_if(BlockType::Result(ValType::I32));
952        emitter.instruction(Instruction::I32Const(1));
953        emitter.emit_else();
954    }
955    emitter.instruction(Instruction::LocalGet(value_local));
956    emitter.instruction(Instruction::RefTestNonNull(target.heap_type));
957    if allows_null {
958        emitter.emit_end();
959    };
960    Ok(())
961}
962
963/// The read of a class field whose declaration narrows an inherited one. The
964/// slot is shared with the parent's declaration, so it can hold a value this
965/// declaration does not admit (spec.md §Classes) — test before casting and throw
966/// a named, catchable `TypeError` instead of letting `ref.cast` raise a bare
967/// `cast failure`. The raw payload slot value is on the stack.
968///
969/// Pushes i32 1 if the object in `obj_local` carries property `fname` at a value
970/// conforming to `field`, 0 otherwise.
971///
972/// A data slot is read and tested. No data slot is not the same as "absent": an
973/// accessor-backed property has no slot, and the value it would answer with is
974/// behind a getter this must not call — a conformance predicate that runs user
975/// code could throw or have side effects. The accessor slot's existence is
976/// therefore accepted here; the accessor call validates its erased return at
977/// the actual read. Neither slot means the property really is missing, and only
978/// an optional field tolerates that.
979///
980/// The read counterpart is `expr::emit_object_property_read`, which makes the
981/// same three-way distinction and calls the getter because it needs the value.
982fn emit_field_conformance(
983    emitter: &mut FunctionEmitter,
984    ctx: &CodegenCtx,
985    locals: FieldConformanceLocals,
986    ordered: diagnostic::OrderedField<'_>,
987    interface_stack: &mut std::collections::BTreeSet<Type>,
988    validator_state: Option<ValidatorState>,
989) -> Result<(), crate::compiler_error::CompilerFailure> {
990    let (fname, field) = (ordered.name, ordered.field);
991    let checkpoint = diagnostic::checkpoint(emitter, ctx);
992    let parent_path = diagnostic::field(emitter, ctx, ordered);
993    let i32_block = BlockType::Result(ValType::I32);
994    let intrinsics = ctx
995        .symbols
996        .intrinsic_type_indices()
997        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
998    let name_global = ctx
999        .symbols
1000        .field_name_string_global_idx(fname)
1001        .ok_or_else(|| {
1002            crate::codegen::internal_failure(
1003                "per-name string global recorded for cast-target field",
1004            )
1005        })?;
1006    let index_local = emitter.add_anonymous_local(ValType::I32)?;
1007    crate::codegen::function_emitter::expr::emit_object_field_index_by_name(
1008        emitter,
1009        ctx,
1010        locals.object,
1011        name_global,
1012    );
1013    emitter.instruction(Instruction::LocalTee(index_local));
1014    emitter.instruction(Instruction::I32Const(0));
1015    emitter.instruction(Instruction::I32GeS);
1016    emitter.emit_if(i32_block);
1017    crate::codegen::function_emitter::expr::emit_field_slot_get(
1018        emitter,
1019        intrinsics,
1020        locals.object,
1021        index_local,
1022    );
1023    emitter.instruction(Instruction::LocalSet(locals.field));
1024    emit_structural_test_inner(
1025        emitter,
1026        ctx,
1027        locals.field,
1028        &field.ty,
1029        interface_stack,
1030        validator_state,
1031    )?;
1032    // A missing key reads as `undefined`, so a field that admits it conforms
1033    // without one: `{ parent: number | undefined }` round-trips through JSON.
1034    let may_be_absent = field.optional || admits_undefined(&field.ty);
1035    if may_be_absent {
1036        emitter.instruction(Instruction::LocalGet(locals.field));
1037        crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
1038        emitter.instruction(Instruction::I32Or);
1039    } else {
1040        // A required undefined-valued property must still exist on the object.
1041        emitter.instruction(Instruction::LocalGet(locals.object));
1042        emitter.instruction(Instruction::StructGet {
1043            struct_type_index: intrinsics.object_shape,
1044            field_index: 1,
1045        });
1046        emitter.instruction(Instruction::LocalGet(index_local));
1047        emitter.instruction(Instruction::ArrayGet(intrinsics.field_names));
1048        crate::codegen::field_names::emit_name_presence(emitter, ctx)?;
1049        emitter.instruction(Instruction::LocalGet(locals.field));
1050        crate::codegen::function_emitter::expr::emit_is_nullish(emitter, ctx)?;
1051        emitter.instruction(Instruction::I32Eqz);
1052        emitter.instruction(Instruction::I32Or);
1053        emitter.instruction(Instruction::I32And);
1054    }
1055    emitter.emit_else();
1056    let getter = crate::codegen::classes::accessor_getter_name(fname);
1057    if may_be_absent {
1058        // Absent and accessor-backed both conform, so the branch is constant.
1059        emitter.instruction(Instruction::I32Const(1));
1060    } else if crate::codegen::function_emitter::expr::accessor_branch_emittable(ctx, &getter) {
1061        let _ = crate::codegen::function_emitter::expr::emit_is_accessor_backed(
1062            emitter,
1063            ctx,
1064            locals.object,
1065            &getter,
1066            crate::AccessorKind::Get,
1067        )?;
1068    } else {
1069        emitter.instruction(Instruction::I32Const(0));
1070    }
1071    emitter.emit_end();
1072    // Reached unset only when the field itself is missing: a present value's
1073    // own test records what it expected.
1074    diagnostic::finish(
1075        emitter,
1076        ctx,
1077        checkpoint,
1078        diagnostic::Tested::Absent { ty: &field.ty },
1079    )?;
1080    diagnostic::pop(emitter, parent_path);
1081    Ok(())
1082}
1083
1084pub fn emit_narrowed_field_read(
1085    emitter: &mut FunctionEmitter,
1086    ctx: &CodegenCtx,
1087    check: &crate::FieldNarrowingCheck,
1088    result_ty: &Type,
1089) -> Result<(), crate::compiler_error::CompilerFailure> {
1090    emit_narrowed_field_read_as(emitter, ctx, check, result_ty, result_ty)?;
1091    Ok(())
1092}
1093
1094pub(crate) fn emit_narrowed_field_read_as(
1095    emitter: &mut FunctionEmitter,
1096    ctx: &CodegenCtx,
1097    check: &crate::FieldNarrowingCheck,
1098    result_ty: &Type,
1099    check_ty: &Type,
1100) -> Result<(), crate::compiler_error::CompilerFailure> {
1101    let result_val = ctx.symbols.value_type(result_ty)?;
1102    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
1103    emitter.instruction(Instruction::LocalSet(scratch));
1104    // Prefer the concrete read-time descriptor. It includes generic
1105    // substitutions and nested interface metadata that the declaration could
1106    // not know; imported legacy declarations fall back to their recorded test.
1107    let runtime_test = ctx.ta.runtime_type_tests.get(check_ty.lowered());
1108    let test = if check
1109        .minimal_test_target
1110        .as_ref()
1111        .is_some_and(|target| target.lowered() == check_ty.lowered())
1112    {
1113        if matches!(check.test, crate::FieldNarrowingTest::NonNull) {
1114            &check.test
1115        } else {
1116            &crate::FieldNarrowingTest::Representation
1117        }
1118    } else {
1119        match runtime_test {
1120            Some(crate::FieldNarrowingTest::Representation)
1121                if !matches!(&check.test, crate::FieldNarrowingTest::Representation) =>
1122            {
1123                &check.test
1124            }
1125            Some(test) => test,
1126            None => &check.test,
1127        }
1128    };
1129    // Representation and null checks cannot descend to a nested location.
1130    // Keep ordinary property reads free of unused diagnostic state.
1131    let previous_diagnostic = emitter.cast_diagnostic.take();
1132    let nested_check = match test {
1133        crate::FieldNarrowingTest::Shape(ty) => diagnostic::has_nested_paths(ty),
1134        crate::FieldNarrowingTest::Interface(_) => true,
1135        _ => false,
1136    };
1137    if nested_check {
1138        diagnostic::begin(emitter, ctx);
1139    }
1140    match test {
1141        // Presence is the check only where the read's type rejects `null`. On a
1142        // generic class the declaration cannot say: `Sub<T>`'s `v: T` is
1143        // `string | null` at `Sub<string | null>`, where a `null` is legal and
1144        // the cast lets it through anyway. `result_ty` is substituted, so it
1145        // answers what the declaration could not.
1146        crate::FieldNarrowingTest::NonNull => {
1147            if cast::target_allows_null(ctx, check_ty)? {
1148                emitter.instruction(Instruction::I32Const(1));
1149            } else {
1150                emit_is_non_null(emitter, scratch);
1151            }
1152        }
1153        crate::FieldNarrowingTest::Shape(shape) => {
1154            emit_structural_test(emitter, ctx, scratch, shape, check_ty)?;
1155            // `emit_cast_to` lifts to non-null for every target it does not admit
1156            // a null for, so those are exactly the targets a `null` in the slot
1157            // would trap on.
1158            if !cast::target_allows_null(ctx, check_ty)? {
1159                emit_is_non_null(emitter, scratch);
1160                emitter.instruction(Instruction::I32And);
1161            }
1162        }
1163        crate::FieldNarrowingTest::Interface(test) => {
1164            emit_interface_test(emitter, ctx, scratch, check_ty, test)?;
1165        }
1166        crate::FieldNarrowingTest::Substituted => {
1167            emit_representation_test(emitter, ctx, scratch, check_ty)?;
1168        }
1169        crate::FieldNarrowingTest::Representation => {
1170            emit_representation_test(emitter, ctx, scratch, check_ty)?;
1171        }
1172    }
1173    emitter.emit_if(BlockType::Result(result_val));
1174    emitter.instruction(Instruction::LocalGet(scratch));
1175    cast::emit_cast_to(emitter, ctx, result_ty)?;
1176    emitter.emit_else();
1177    emit_inline_string(emitter, ctx, &check.message);
1178    diagnostic::append_failure(emitter, ctx);
1179    emit_type_error_from_message(emitter, ctx);
1180    emitter.emit_end();
1181    emitter.cast_diagnostic = previous_diagnostic;
1182    Ok(())
1183}
1184
1185/// `ty` is the type `test` checks, named if the check exhausts its step limit.
1186fn emit_interface_test(
1187    emitter: &mut FunctionEmitter,
1188    ctx: &CodegenCtx,
1189    value_local: u32,
1190    ty: &Type,
1191    test: &crate::InterfaceNarrowingTest,
1192) -> Result<(), crate::compiler_error::CompilerFailure> {
1193    ctx.checking(ty, || {
1194        emit_interface_test_inner(
1195            emitter,
1196            ctx,
1197            value_local,
1198            ty,
1199            test,
1200            &mut std::collections::BTreeSet::new(),
1201            None,
1202        )?;
1203        ctx.check_function_limits(emitter)
1204    })
1205}
1206
1207fn emit_interface_test_inner(
1208    emitter: &mut FunctionEmitter,
1209    ctx: &CodegenCtx,
1210    value_local: u32,
1211    ty: &Type,
1212    test: &crate::InterfaceNarrowingTest,
1213    interface_stack: &mut std::collections::BTreeSet<Type>,
1214    validator_state: Option<ValidatorState>,
1215) -> Result<(), crate::compiler_error::CompilerFailure> {
1216    ctx.charge_validator_step(emitter)?;
1217    let checkpoint = diagnostic::checkpoint(emitter, ctx);
1218    if test.nullable || test.undefined {
1219        emitter.instruction(Instruction::LocalGet(value_local));
1220        if test.nullable && test.undefined {
1221            crate::codegen::function_emitter::expr::emit_is_nullish(emitter, ctx)?;
1222        } else if test.undefined {
1223            crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
1224        } else {
1225            emitter.instruction(Instruction::RefIsNull);
1226        }
1227        emitter.emit_if(BlockType::Result(ValType::I32));
1228        emitter.instruction(Instruction::I32Const(1));
1229        emitter.emit_else();
1230    }
1231
1232    if !test.shape_allowed {
1233        emit_non_shape_interface_test(emitter, ctx, value_local, test, validator_state)?;
1234        if test.nullable || test.undefined {
1235            emitter.emit_end();
1236        }
1237        return Ok(());
1238    }
1239
1240    let shape_idx = ctx
1241        .symbols
1242        .object_shape_type_idx()
1243        .ok_or_else(|| crate::codegen::internal_failure("$ObjectShape intrinsic registered"))?;
1244    emitter.instruction(Instruction::LocalGet(value_local));
1245    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(shape_idx)));
1246    emitter.emit_if(BlockType::Result(ValType::I32));
1247    let object_local = emitter.add_anonymous_local(ValType::Ref(RefType {
1248        nullable: false,
1249        heap_type: HeapType::Concrete(shape_idx),
1250    }))?;
1251    emitter.instruction(Instruction::LocalGet(value_local));
1252    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(shape_idx)));
1253    emitter.instruction(Instruction::LocalSet(object_local));
1254    let field_local = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
1255    emitter.instruction(Instruction::I32Const(1));
1256    for ordered in diagnostic::check_order(emitter, &test.members) {
1257        let (name, field) = (ordered.name, ordered.field);
1258        if test.methods.contains(name) {
1259            let parent_path = diagnostic::field(emitter, ctx, ordered);
1260            let name_global = ctx
1261                .symbols
1262                .field_name_string_global_idx(name)
1263                .ok_or_else(|| {
1264                    crate::codegen::internal_failure("interface narrowing method name registered")
1265                })?;
1266            crate::codegen::function_emitter::expr::emit_object_field_read_by_name(
1267                emitter,
1268                ctx,
1269                object_local,
1270                name_global,
1271            );
1272            emitter.instruction(Instruction::LocalSet(field_local));
1273            emit_structural_test_inner(
1274                emitter,
1275                ctx,
1276                field_local,
1277                &field.ty,
1278                interface_stack,
1279                validator_state,
1280            )?;
1281            diagnostic::pop(emitter, parent_path);
1282        } else {
1283            emit_field_conformance(
1284                emitter,
1285                ctx,
1286                FieldConformanceLocals {
1287                    object: object_local,
1288                    field: field_local,
1289                },
1290                ordered,
1291                interface_stack,
1292                validator_state,
1293            )?;
1294        }
1295        emitter.instruction(Instruction::I32And);
1296    }
1297    if let Some(index) = &test.index {
1298        emit_index_conformance(
1299            emitter,
1300            ctx,
1301            object_local,
1302            index,
1303            interface_stack,
1304            validator_state,
1305        )?;
1306        emitter.instruction(Instruction::I32And);
1307    }
1308    // Some direct-dispatch interfaces intentionally use `$ObjectShape` only as
1309    // an inert receiver carrier (for example TextEncoder/TextDecoder); their
1310    // methods are host imports, not payload slots. Admit those exact carriers
1311    // alongside ordinary structural conformance.
1312    emit_non_shape_interface_test(emitter, ctx, value_local, test, validator_state)?;
1313    emitter.instruction(Instruction::I32Or);
1314    emitter.emit_else();
1315    emit_non_shape_interface_test(emitter, ctx, value_local, test, validator_state)?;
1316    emitter.emit_end();
1317
1318    if test.nullable || test.undefined {
1319        emitter.emit_end();
1320    }
1321    diagnostic::finish(
1322        emitter,
1323        ctx,
1324        checkpoint,
1325        diagnostic::Tested::Value {
1326            ty,
1327            local: value_local,
1328        },
1329    )?;
1330    Ok(())
1331}
1332
1333fn emit_non_shape_interface_test(
1334    emitter: &mut FunctionEmitter,
1335    ctx: &CodegenCtx,
1336    value_local: u32,
1337    test: &crate::InterfaceNarrowingTest,
1338    validator_state: Option<ValidatorState>,
1339) -> Result<(), crate::compiler_error::CompilerFailure> {
1340    use crate::InterfaceCarrier;
1341
1342    let matches_any = emitter.add_anonymous_local(ValType::I32)?;
1343    emitter.instruction(Instruction::I32Const(0));
1344    emitter.instruction(Instruction::LocalSet(matches_any));
1345    for carrier in &test.non_shape_carriers {
1346        let intr = ctx
1347            .symbols
1348            .intrinsic_type_indices()
1349            .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1350        match carrier {
1351            InterfaceCarrier::ArrayAny => emit_structural_test_inner(
1352                emitter,
1353                ctx,
1354                value_local,
1355                &Type::Array(Box::new(Type::Unknown)),
1356                &mut std::collections::BTreeSet::new(),
1357                validator_state,
1358            )?,
1359            InterfaceCarrier::Array(ty) => {
1360                emit_structural_test_inner(
1361                    emitter,
1362                    ctx,
1363                    value_local,
1364                    ty,
1365                    &mut std::collections::BTreeSet::new(),
1366                    validator_state,
1367                )?;
1368            }
1369            InterfaceCarrier::Number => emit_ref_test(emitter, value_local, boxed_number_idx(ctx)?),
1370            InterfaceCarrier::Boolean => {
1371                emit_ref_test(emitter, value_local, boxed_boolean_idx(ctx)?);
1372            }
1373            InterfaceCarrier::String => emit_ref_test(emitter, value_local, string_idx(ctx)?),
1374            InterfaceCarrier::BigInt => emit_ref_test(emitter, value_local, intr.bigint),
1375            InterfaceCarrier::Uint8Array => emit_ref_test(emitter, value_local, intr.uint8_array),
1376            InterfaceCarrier::MapAny => emit_ref_test(emitter, value_local, intr.map),
1377            InterfaceCarrier::Map(key, value) => {
1378                emit_map_carrier_test(emitter, ctx, value_local, key, value, validator_state)?;
1379            }
1380            InterfaceCarrier::SetAny => emit_ref_test(emitter, value_local, intr.set),
1381            InterfaceCarrier::Set(element) => {
1382                emit_set_carrier_test(emitter, ctx, value_local, element, validator_state)?;
1383            }
1384            InterfaceCarrier::RegExp => emit_ref_test(emitter, value_local, intr.regex),
1385            InterfaceCarrier::RegExpMatch => {
1386                emit_regexp_array_test(emitter, ctx, value_local, test, validator_state)?;
1387                emit_ref_test(emitter, value_local, intr.regex_match_box);
1388                emitter.instruction(Instruction::I32And);
1389            }
1390            InterfaceCarrier::RegExpMatchArray => {
1391                emit_regexp_array_test(emitter, ctx, value_local, test, validator_state)?;
1392            }
1393            InterfaceCarrier::TemporalInstant => {
1394                emit_ref_test(emitter, value_local, intr.temporal_instant);
1395            }
1396            InterfaceCarrier::TemporalDuration => {
1397                emit_ref_test(emitter, value_local, intr.temporal_duration);
1398            }
1399            InterfaceCarrier::TemporalZonedDateTime => {
1400                emit_ref_test(emitter, value_local, intr.temporal_zdt);
1401            }
1402            InterfaceCarrier::TemporalPlainDate => {
1403                emit_ref_test(emitter, value_local, intr.temporal_plain_date);
1404            }
1405            InterfaceCarrier::TemporalPlainTime => {
1406                emit_ref_test(emitter, value_local, intr.temporal_plain_time);
1407            }
1408            InterfaceCarrier::TemporalPlainDateTime => {
1409                emit_ref_test(emitter, value_local, intr.temporal_plain_date_time);
1410            }
1411            InterfaceCarrier::TemporalPlainYearMonth => {
1412                emit_ref_test(emitter, value_local, intr.temporal_plain_year_month);
1413            }
1414            InterfaceCarrier::TemporalPlainMonthDay => {
1415                emit_ref_test(emitter, value_local, intr.temporal_plain_month_day);
1416            }
1417            InterfaceCarrier::ObjectShape => {
1418                emit_ref_test(emitter, value_local, intr.object_shape);
1419            }
1420            InterfaceCarrier::Url => emit_ref_test(emitter, value_local, intr.url),
1421            InterfaceCarrier::FsStat => emit_ref_test(emitter, value_local, intr.fs_stat),
1422            InterfaceCarrier::FsPeek => emit_ref_test(emitter, value_local, intr.fs_peek),
1423            InterfaceCarrier::FsDirEntry => emit_ref_test(emitter, value_local, intr.fs_dir_entry),
1424            InterfaceCarrier::FsInfo => emit_ref_test(emitter, value_local, intr.fs_info),
1425            InterfaceCarrier::FsMountInfo => {
1426                emit_ref_test(emitter, value_local, intr.fs_mount_info);
1427            }
1428            InterfaceCarrier::FsFileWriter => {
1429                emit_ref_test(emitter, value_local, intr.fs_file_writer);
1430            }
1431            InterfaceCarrier::HttpResponse => {
1432                emit_ref_test(emitter, value_local, intr.http_response);
1433            }
1434            InterfaceCarrier::HttpDownloadResult => {
1435                emit_ref_test(emitter, value_local, intr.http_download_result);
1436            }
1437            InterfaceCarrier::SessionEntry => {
1438                emit_ref_test(emitter, value_local, intr.session_entry);
1439            }
1440            InterfaceCarrier::SessionPage => emit_ref_test(emitter, value_local, intr.session_page),
1441        }
1442        emitter.instruction(Instruction::LocalGet(matches_any));
1443        emitter.instruction(Instruction::I32Or);
1444        emitter.instruction(Instruction::LocalSet(matches_any));
1445    }
1446    emitter.instruction(Instruction::LocalGet(matches_any));
1447    Ok(())
1448}
1449
1450/// The whole match, captures and named groups remain mutable through widened
1451/// aliases, so a checked cast validates their contents even on a branded result.
1452fn emit_regexp_array_test(
1453    emitter: &mut FunctionEmitter,
1454    ctx: &CodegenCtx,
1455    value_local: u32,
1456    test: &crate::InterfaceNarrowingTest,
1457    validator_state: Option<ValidatorState>,
1458) -> Result<(), crate::compiler_error::CompilerFailure> {
1459    emit_structural_test_inner(
1460        emitter,
1461        ctx,
1462        value_local,
1463        &Type::Array(Box::new(Type::regexp_element())),
1464        &mut std::collections::BTreeSet::new(),
1465        validator_state,
1466    )?;
1467    let intr = ctx
1468        .symbols
1469        .intrinsic_type_indices()
1470        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1471    // The successful element test establishes the array carrier. Nothing calls
1472    // guest code between that test and these length/storage reads.
1473    emitter.emit_if(BlockType::Result(ValType::I32));
1474    emit_regexp_whole_match_test(emitter, ctx, value_local)?;
1475    emitter.emit_else();
1476    emitter.instruction(Instruction::I32Const(0));
1477    emitter.emit_end();
1478    emit_ref_test(emitter, value_local, intr.regex_match_box);
1479    emitter.emit_if(BlockType::Result(ValType::I32));
1480    let groups_local = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
1481    emitter.instruction(Instruction::LocalGet(value_local));
1482    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
1483        intr.regex_match_box,
1484    )));
1485    emitter.instruction(Instruction::StructGet {
1486        struct_type_index: intr.regex_match_box,
1487        field_index: 5,
1488    });
1489    emitter.instruction(Instruction::LocalSet(groups_local));
1490    let groups = test
1491        .members
1492        .get("groups")
1493        .ok_or_else(|| crate::codegen::internal_failure("regex array declares groups"))?;
1494    emit_structural_test_inner(
1495        emitter,
1496        ctx,
1497        groups_local,
1498        &groups.ty,
1499        &mut std::collections::BTreeSet::new(),
1500        validator_state,
1501    )?;
1502    emitter.emit_else();
1503    emitter.instruction(Instruction::I32Const(1));
1504    emitter.emit_end();
1505    emitter.instruction(Instruction::I32And);
1506    Ok(())
1507}
1508
1509/// Called only after the array carrier and its elements have been validated.
1510fn emit_regexp_whole_match_test(
1511    emitter: &mut FunctionEmitter,
1512    ctx: &CodegenCtx,
1513    value_local: u32,
1514) -> Result<(), crate::compiler_error::CompilerFailure> {
1515    let intr = ctx
1516        .symbols
1517        .intrinsic_type_indices()
1518        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1519    let array_idx = ctx
1520        .symbols
1521        .array_type_idx()
1522        .ok_or_else(|| crate::codegen::internal_failure("$Array intrinsic registered"))?;
1523    emitter.instruction(Instruction::LocalGet(value_local));
1524    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(array_idx)));
1525    emitter.instruction(Instruction::StructGet {
1526        struct_type_index: array_idx,
1527        field_index: 2,
1528    });
1529    // A nonempty logical array has a backing slot zero. Check before loading it
1530    // so empty/truncated arrays fail the cast instead of trapping on the read.
1531    emitter.emit_if(BlockType::Result(ValType::I32));
1532    emitter.instruction(Instruction::LocalGet(value_local));
1533    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(array_idx)));
1534    emitter.instruction(Instruction::StructGet {
1535        struct_type_index: array_idx,
1536        field_index: 1,
1537    });
1538    emitter.instruction(Instruction::I32Const(0));
1539    emitter.instruction(Instruction::ArrayGet(intr.raw_array));
1540    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(string_idx(
1541        ctx,
1542    )?)));
1543    emitter.emit_else();
1544    emitter.instruction(Instruction::I32Const(0));
1545    emitter.emit_end();
1546    Ok(())
1547}
1548
1549fn emit_set_carrier_test(
1550    emitter: &mut FunctionEmitter,
1551    ctx: &CodegenCtx,
1552    value_local: u32,
1553    element: &Type,
1554    validator_state: Option<ValidatorState>,
1555) -> Result<(), crate::compiler_error::CompilerFailure> {
1556    let intr = ctx
1557        .symbols
1558        .intrinsic_type_indices()
1559        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1560    emit_ref_test(emitter, value_local, intr.set);
1561    emitter.emit_if(BlockType::Result(ValType::I32));
1562    let backing = emitter.add_anonymous_local(ValType::Ref(RefType {
1563        nullable: false,
1564        heap_type: HeapType::Concrete(intr.set),
1565    }))?;
1566    let elements = emitter.add_anonymous_local(ValType::Ref(RefType {
1567        nullable: false,
1568        heap_type: HeapType::Concrete(intr.raw_array),
1569    }))?;
1570    let order = emitter.add_anonymous_local(ValType::Ref(RefType {
1571        nullable: false,
1572        heap_type: HeapType::Concrete(intr.raw_index_array),
1573    }))?;
1574    emitter.instruction(Instruction::LocalGet(value_local));
1575    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(intr.set)));
1576    emitter.instruction(Instruction::LocalTee(backing));
1577    emitter.instruction(Instruction::StructGet {
1578        struct_type_index: intr.set,
1579        field_index: 1,
1580    });
1581    emitter.instruction(Instruction::LocalSet(elements));
1582    emitter.instruction(Instruction::LocalGet(backing));
1583    emitter.instruction(Instruction::StructGet {
1584        struct_type_index: intr.set,
1585        field_index: 3,
1586    });
1587    emitter.instruction(Instruction::LocalSet(order));
1588    emit_ordered_collection_members_test(
1589        emitter,
1590        ctx,
1591        CollectionStorage {
1592            backing,
1593            backing_type: intr.set,
1594            order_len_field: 4,
1595            order,
1596        },
1597        &[(elements, element)],
1598        validator_state,
1599    )?;
1600    emitter.emit_else();
1601    emitter.instruction(Instruction::I32Const(0));
1602    emitter.emit_end();
1603    Ok(())
1604}
1605
1606fn emit_map_carrier_test(
1607    emitter: &mut FunctionEmitter,
1608    ctx: &CodegenCtx,
1609    value_local: u32,
1610    key: &Type,
1611    value: &Type,
1612    validator_state: Option<ValidatorState>,
1613) -> Result<(), crate::compiler_error::CompilerFailure> {
1614    let intr = ctx
1615        .symbols
1616        .intrinsic_type_indices()
1617        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1618    emit_ref_test(emitter, value_local, intr.map);
1619    emitter.emit_if(BlockType::Result(ValType::I32));
1620    let backing = emitter.add_anonymous_local(ValType::Ref(RefType {
1621        nullable: false,
1622        heap_type: HeapType::Concrete(intr.map),
1623    }))?;
1624    let keys = emitter.add_anonymous_local(ValType::Ref(RefType {
1625        nullable: false,
1626        heap_type: HeapType::Concrete(intr.raw_array),
1627    }))?;
1628    let values = emitter.add_anonymous_local(ValType::Ref(RefType {
1629        nullable: false,
1630        heap_type: HeapType::Concrete(intr.raw_array),
1631    }))?;
1632    let order = emitter.add_anonymous_local(ValType::Ref(RefType {
1633        nullable: false,
1634        heap_type: HeapType::Concrete(intr.raw_index_array),
1635    }))?;
1636    emitter.instruction(Instruction::LocalGet(value_local));
1637    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(intr.map)));
1638    emitter.instruction(Instruction::LocalTee(backing));
1639    emitter.instruction(Instruction::StructGet {
1640        struct_type_index: intr.map,
1641        field_index: 1,
1642    });
1643    emitter.instruction(Instruction::LocalSet(keys));
1644    emitter.instruction(Instruction::LocalGet(backing));
1645    emitter.instruction(Instruction::StructGet {
1646        struct_type_index: intr.map,
1647        field_index: 2,
1648    });
1649    emitter.instruction(Instruction::LocalSet(values));
1650    emitter.instruction(Instruction::LocalGet(backing));
1651    emitter.instruction(Instruction::StructGet {
1652        struct_type_index: intr.map,
1653        field_index: 4,
1654    });
1655    emitter.instruction(Instruction::LocalSet(order));
1656    emit_ordered_collection_members_test(
1657        emitter,
1658        ctx,
1659        CollectionStorage {
1660            backing,
1661            backing_type: intr.map,
1662            order_len_field: 5,
1663            order,
1664        },
1665        &[(keys, key), (values, value)],
1666        validator_state,
1667    )?;
1668    emitter.emit_else();
1669    emitter.instruction(Instruction::I32Const(0));
1670    emitter.emit_end();
1671    Ok(())
1672}
1673
1674fn emit_ordered_collection_members_test(
1675    emitter: &mut FunctionEmitter,
1676    ctx: &CodegenCtx,
1677    storage: CollectionStorage,
1678    payloads: &[(u32, &Type)],
1679    validator_state: Option<ValidatorState>,
1680) -> Result<(), crate::compiler_error::CompilerFailure> {
1681    let intr = ctx
1682        .symbols
1683        .intrinsic_type_indices()
1684        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1685    let acc = emitter.add_anonymous_local(ValType::I32)?;
1686    let cursor = emitter.add_anonymous_local(ValType::I32)?;
1687    let slot = emitter.add_anonymous_local(ValType::I32)?;
1688    let member = emitter.add_anonymous_local(scratch_object_ty(intr.object))?;
1689    emitter.instruction(Instruction::I32Const(1));
1690    emitter.instruction(Instruction::LocalSet(acc));
1691    emitter.instruction(Instruction::I32Const(0));
1692    emitter.instruction(Instruction::LocalSet(cursor));
1693    emitter.emit_block(BlockType::Empty);
1694    emitter.emit_loop(BlockType::Empty);
1695    emitter.instruction(Instruction::LocalGet(cursor));
1696    emitter.instruction(Instruction::LocalGet(storage.backing));
1697    emitter.instruction(Instruction::StructGet {
1698        struct_type_index: storage.backing_type,
1699        field_index: storage.order_len_field,
1700    });
1701    emitter.instruction(Instruction::I32GeU);
1702    emitter.instruction(Instruction::BrIf(1));
1703    emitter.instruction(Instruction::LocalGet(storage.order));
1704    emitter.instruction(Instruction::LocalGet(cursor));
1705    emitter.instruction(Instruction::ArrayGet(intr.raw_index_array));
1706    emitter.instruction(Instruction::LocalTee(slot));
1707    emitter.instruction(Instruction::I32Const(0));
1708    emitter.instruction(Instruction::I32GeS);
1709    emitter.emit_if(BlockType::Empty);
1710    for (position, (payload, ty)) in payloads.iter().enumerate() {
1711        let parent_path = diagnostic::index(emitter, ctx, cursor);
1712        let member_path = diagnostic::entry_part(
1713            emitter,
1714            ctx,
1715            if payloads.len() == 1 || position == 1 {
1716                "value"
1717            } else {
1718                "key"
1719            },
1720        );
1721        emitter.instruction(Instruction::LocalGet(*payload));
1722        emitter.instruction(Instruction::LocalGet(slot));
1723        emitter.instruction(Instruction::ArrayGet(intr.raw_array));
1724        emitter.instruction(Instruction::LocalSet(member));
1725        emitter.instruction(Instruction::LocalGet(acc));
1726        emit_structural_test_inner(
1727            emitter,
1728            ctx,
1729            member,
1730            ty,
1731            &mut std::collections::BTreeSet::new(),
1732            validator_state,
1733        )?;
1734        diagnostic::pop(emitter, member_path);
1735        diagnostic::pop(emitter, parent_path);
1736        emitter.instruction(Instruction::I32And);
1737        emitter.instruction(Instruction::LocalSet(acc));
1738    }
1739    emitter.emit_end();
1740    emitter.instruction(Instruction::LocalGet(cursor));
1741    emitter.instruction(Instruction::I32Const(1));
1742    emitter.instruction(Instruction::I32Add);
1743    emitter.instruction(Instruction::LocalSet(cursor));
1744    emitter.instruction(Instruction::Br(0));
1745    emitter.emit_end();
1746    emitter.emit_end();
1747    emitter.instruction(Instruction::LocalGet(acc));
1748    Ok(())
1749}
1750
1751fn emit_validator_state(
1752    emitter: &mut FunctionEmitter,
1753    ctx: &CodegenCtx,
1754    validator_state: Option<ValidatorState>,
1755) -> Result<u32, crate::compiler_error::CompilerFailure> {
1756    let intr = ctx
1757        .symbols
1758        .intrinsic_type_indices()
1759        .ok_or_else(|| super::internal_failure("intrinsic types are not declared"))?;
1760    let visited = if let Some(state) = validator_state {
1761        state.visited
1762    } else {
1763        let local = emitter.add_anonymous_local(ValType::Ref(RefType {
1764            nullable: false,
1765            heap_type: HeapType::Concrete(intr.raw_array),
1766        }))?;
1767        emitter.instruction(Instruction::I32Const(
1768            RECURSIVE_VALIDATOR_CAPACITY * 2 + diagnostic::SLOTS,
1769        ));
1770        emitter.instruction(Instruction::ArrayNewDefault(intr.raw_array));
1771        emitter.instruction(Instruction::LocalSet(local));
1772        local
1773    };
1774    diagnostic::save_recursive(emitter, ctx, visited)?;
1775    emitter.instruction(Instruction::LocalGet(visited));
1776    if let Some(state) = validator_state {
1777        emitter.instruction(Instruction::LocalGet(state.validator_ids));
1778        emitter.instruction(Instruction::LocalGet(state.depth));
1779        emitter.instruction(Instruction::I32Const(1));
1780        emitter.instruction(Instruction::I32Add);
1781    } else {
1782        emitter.instruction(Instruction::I32Const(RECURSIVE_VALIDATOR_CAPACITY));
1783        emitter.instruction(Instruction::ArrayNewDefault(intr.raw_index_array));
1784        emitter.instruction(Instruction::I32Const(0));
1785    }
1786    Ok(visited)
1787}
1788
1789/// Body of a recursive runtime validator:
1790/// `(ref null $Object, ref $rawArray visited, ref $rawIndexArray ids, i32 depth, ref $ObjectFields types) -> i32`.
1791/// A `(value, validator)` pair already present on the active recursion path
1792/// closes a valid cycle without conflating mutually recursive shapes.
1793/// The fixed-capacity path rejects an implausibly deep acyclic graph rather than
1794/// overflowing the Wasm stack or accepting an unchecked tail.
1795pub(crate) fn emit_runtime_validator_body(
1796    ctx: &CodegenCtx,
1797    key: &Type,
1798    body: &Type,
1799    rejects_polymorphic_edge: bool,
1800    validator_id: i32,
1801) -> Result<Function, crate::compiler_error::CompilerFailure> {
1802    let intr = ctx
1803        .symbols
1804        .intrinsic_type_indices()
1805        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1806    let object_idx = intr.object;
1807    let object_ref_null = ValType::Ref(RefType {
1808        nullable: true,
1809        heap_type: HeapType::Concrete(object_idx),
1810    });
1811    let raw_array_ref = ValType::Ref(RefType {
1812        nullable: false,
1813        heap_type: HeapType::Concrete(intr.raw_array),
1814    });
1815    let raw_index_array_ref = ValType::Ref(RefType {
1816        nullable: false,
1817        heap_type: HeapType::Concrete(intr.raw_index_array),
1818    });
1819    let ident = |name: &str| Ident {
1820        name: name.to_string(),
1821        span: Span::at(ctx.file),
1822    };
1823    let mut emitter = FunctionEmitter::new(
1824        ctx,
1825        &[
1826            (ident("v"), object_ref_null),
1827            (ident("visited"), raw_array_ref),
1828            (ident("validator_ids"), raw_index_array_ref),
1829            (ident("depth"), ValType::I32),
1830            (
1831                ident("types"),
1832                super::runtime_descriptors::environment_type(ctx.symbols)?,
1833            ),
1834        ],
1835    )?;
1836    super::runtime_descriptors::bind(
1837        &mut emitter,
1838        &super::runtime_descriptors::parameters(key),
1839        4,
1840    )?;
1841    if rejects_polymorphic_edge {
1842        emitter.instruction(Instruction::I32Const(0));
1843        return emitter.build();
1844    }
1845    diagnostic::enter_recursive(&mut emitter, ctx);
1846    let checkpoint = diagnostic::checkpoint(&mut emitter, ctx);
1847    let parameters = super::runtime_descriptors::parameters(key);
1848    let seen = emitter.add_anonymous_local(ValType::I32)?;
1849    let index = emitter.add_anonymous_local(ValType::I32)?;
1850    emitter.instruction(Instruction::I32Const(0));
1851    emitter.instruction(Instruction::LocalSet(seen));
1852    emitter.instruction(Instruction::I32Const(0));
1853    emitter.instruction(Instruction::LocalSet(index));
1854    emitter.emit_block(BlockType::Empty);
1855    emitter.emit_loop(BlockType::Empty);
1856    emitter.instruction(Instruction::LocalGet(index));
1857    emitter.instruction(Instruction::LocalGet(3));
1858    emitter.instruction(Instruction::I32GeU);
1859    emitter.instruction(Instruction::BrIf(1));
1860    emitter.instruction(Instruction::LocalGet(1));
1861    emitter.instruction(Instruction::LocalGet(index));
1862    emitter.instruction(Instruction::I32Const(2));
1863    emitter.instruction(Instruction::I32Mul);
1864    emitter.instruction(Instruction::ArrayGet(intr.raw_array));
1865    emitter.instruction(Instruction::LocalGet(0));
1866    emitter.instruction(Instruction::RefEq);
1867    emitter.instruction(Instruction::LocalGet(2));
1868    emitter.instruction(Instruction::LocalGet(index));
1869    emitter.instruction(Instruction::ArrayGet(intr.raw_index_array));
1870    emitter.instruction(Instruction::I32Const(validator_id));
1871    emitter.instruction(Instruction::I32Eq);
1872    emitter.instruction(Instruction::I32And);
1873    emitter.emit_if(BlockType::Empty);
1874    emit_same_validator_environment(&mut emitter, ctx, index, parameters.len())?;
1875    emitter.instruction(Instruction::LocalSet(seen));
1876    emitter.instruction(Instruction::LocalGet(seen));
1877    emitter.emit_if(BlockType::Empty);
1878    emitter.instruction(Instruction::Br(3));
1879    emitter.emit_end();
1880    emitter.emit_end();
1881    emitter.instruction(Instruction::LocalGet(index));
1882    emitter.instruction(Instruction::I32Const(1));
1883    emitter.instruction(Instruction::I32Add);
1884    emitter.instruction(Instruction::LocalSet(index));
1885    emitter.instruction(Instruction::Br(0));
1886    emitter.emit_end();
1887    emitter.emit_end();
1888    emitter.instruction(Instruction::LocalGet(seen));
1889    emitter.emit_if(BlockType::Result(ValType::I32));
1890    emitter.instruction(Instruction::I32Const(1));
1891    emitter.emit_else();
1892    emitter.instruction(Instruction::LocalGet(3));
1893    emitter.instruction(Instruction::I32Const(RECURSIVE_VALIDATOR_CAPACITY));
1894    emitter.instruction(Instruction::I32GeU);
1895    emitter.emit_if(BlockType::Result(ValType::I32));
1896    emitter.instruction(Instruction::I32Const(0));
1897    emitter.emit_else();
1898    emitter.instruction(Instruction::LocalGet(1));
1899    emitter.instruction(Instruction::LocalGet(3));
1900    emitter.instruction(Instruction::I32Const(2));
1901    emitter.instruction(Instruction::I32Mul);
1902    emitter.instruction(Instruction::LocalGet(0));
1903    emitter.instruction(Instruction::ArraySet(intr.raw_array));
1904    if !parameters.is_empty() {
1905        emit_store_validator_environment(&mut emitter, ctx)?;
1906    }
1907    emitter.instruction(Instruction::LocalGet(2));
1908    emitter.instruction(Instruction::LocalGet(3));
1909    emitter.instruction(Instruction::I32Const(validator_id));
1910    emitter.instruction(Instruction::ArraySet(intr.raw_index_array));
1911    let mut interface_stack = std::collections::BTreeSet::new();
1912    if key.lowered() == body.lowered()
1913        && let Some(crate::FieldNarrowingTest::Interface(test)) =
1914            ctx.ta.runtime_type_tests.get(key.lowered())
1915    {
1916        emit_interface_test_inner(
1917            &mut emitter,
1918            ctx,
1919            0,
1920            key,
1921            test,
1922            &mut interface_stack,
1923            Some(ValidatorState {
1924                visited: 1,
1925                validator_ids: 2,
1926                depth: 3,
1927            }),
1928        )?;
1929    } else {
1930        // A recursive alias's body is checked under the alias's name, so a
1931        // failure at its value names `Tree`, not the expanded shape.
1932        let named_body = match key {
1933            Type::AliasRef {
1934                mangled,
1935                package,
1936                name,
1937                args,
1938            } => Some(Type::alias_ty(
1939                package.clone(),
1940                name.clone(),
1941                mangled.clone(),
1942                args.clone(),
1943                Box::new(body.clone()),
1944            )),
1945            _ => None,
1946        };
1947        emit_structural_test_inner(
1948            &mut emitter,
1949            ctx,
1950            0,
1951            named_body.as_ref().unwrap_or(body),
1952            &mut interface_stack,
1953            Some(ValidatorState {
1954                visited: 1,
1955                validator_ids: 2,
1956                depth: 3,
1957            }),
1958        )?;
1959    }
1960    emitter.emit_end();
1961    emitter.emit_end();
1962    diagnostic::finish(
1963        &mut emitter,
1964        ctx,
1965        checkpoint,
1966        diagnostic::Tested::Value { ty: key, local: 0 },
1967    )?;
1968    diagnostic::save_recursive(&mut emitter, ctx, 1)?;
1969    ctx.check_function_limits(&mut emitter)?;
1970    emitter.build()
1971}
1972
1973fn emit_is_non_null(emitter: &mut FunctionEmitter, value_local: u32) {
1974    emitter.instruction(Instruction::LocalGet(value_local));
1975    emitter.instruction(Instruction::RefIsNull);
1976    emitter.instruction(Instruction::I32Eqz);
1977}
1978
1979fn emit_ref_test(emitter: &mut FunctionEmitter, value_local: u32, type_idx: u32) {
1980    emitter.instruction(Instruction::LocalGet(value_local));
1981    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(type_idx)));
1982}
1983
1984fn boxed_number_idx(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1985    ctx.symbols
1986        .boxed_number_type_idx()
1987        .ok_or_else(|| crate::codegen::internal_failure("$BoxedNumber intrinsic registered"))
1988}
1989
1990fn boxed_boolean_idx(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1991    ctx.symbols
1992        .boxed_boolean_type_idx()
1993        .ok_or_else(|| crate::codegen::internal_failure("$BoxedBoolean intrinsic registered"))
1994}
1995
1996fn string_idx(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1997    ctx.symbols
1998        .string_type_idx()
1999        .ok_or_else(|| crate::codegen::internal_failure("$string intrinsic registered"))
2000}
2001
2002fn object_idx_of(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
2003    Ok(ctx
2004        .symbols
2005        .intrinsic_type_indices()
2006        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?
2007        .object)
2008}
2009
2010fn scratch_object_ty(object_idx: u32) -> ValType {
2011    ValType::Ref(RefType {
2012        nullable: true,
2013        heap_type: HeapType::Concrete(object_idx),
2014    })
2015}
2016
2017fn emit_cast_throw(
2018    emitter: &mut FunctionEmitter,
2019    ctx: &CodegenCtx,
2020    scratch: u32,
2021    target_ty: &Type,
2022) {
2023    ctx.latch(emit_cast_throw_checked(emitter, ctx, scratch, target_ty));
2024}
2025
2026fn emit_cast_throw_checked(
2027    emitter: &mut FunctionEmitter,
2028    ctx: &CodegenCtx,
2029    scratch: u32,
2030    target_ty: &Type,
2031) -> Result<(), crate::compiler_error::CompilerFailure> {
2032    let prefix = error_prefix(target_ty);
2033    emit_inline_string(emitter, ctx, &prefix);
2034
2035    emit_runtime_type_string(emitter, ctx, scratch);
2036
2037    let concat_idx = ctx
2038        .symbols
2039        .prelude_func_idx("string_concat")
2040        .ok_or_else(|| crate::codegen::internal_failure("string_concat imported from prelude"))?;
2041    emitter.instruction(Instruction::Call(concat_idx));
2042
2043    diagnostic::append_failure(emitter, ctx);
2044
2045    emit_type_error_from_message(emitter, ctx);
2046
2047    Ok(())
2048}
2049
2050fn emit_type_error_from_message(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
2051    ctx.latch(emit_type_error_from_message_checked(emitter, ctx));
2052}
2053
2054fn emit_type_error_from_message_checked(
2055    emitter: &mut FunctionEmitter,
2056    ctx: &CodegenCtx,
2057) -> Result<(), crate::compiler_error::CompilerFailure> {
2058    let new_idx = ctx
2059        .symbols
2060        .prelude_func_idx("TypeError#constructor")
2061        .ok_or_else(|| {
2062            crate::codegen::internal_failure("TypeError#constructor imported from prelude")
2063        })?;
2064    crate::codegen::throw::emit_no_error_options(emitter);
2065    emitter.instruction(Instruction::Call(new_idx));
2066
2067    // Constructor returns (ref null $Object); the throw helper narrows to
2068    // $Error so the throw carries the payload type the catch expects.
2069    crate::codegen::throw::emit_error_throw(emitter, ctx);
2070
2071    Ok(())
2072}
2073
2074pub(super) fn emit_inline_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, text: &str) {
2075    ctx.latch(emit_inline_string_checked(emitter, ctx, text));
2076}
2077
2078fn emit_inline_string_checked(
2079    emitter: &mut FunctionEmitter,
2080    ctx: &CodegenCtx,
2081    text: &str,
2082) -> Result<(), crate::compiler_error::CompilerFailure> {
2083    let intr = ctx
2084        .symbols
2085        .intrinsic_type_indices()
2086        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
2087    let string_vtable_idx = ctx
2088        .symbols
2089        .prelude_global_idx("string_vtable")
2090        .ok_or_else(|| crate::codegen::internal_failure("string_vtable imported"))?;
2091    emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
2092    ctx.latch(emit_inline_const_raw_string(emitter, ctx, text));
2093    emitter.instruction(Instruction::I64Const(0));
2094    emitter.instruction(Instruction::StructNew(intr.string));
2095
2096    Ok(())
2097}
2098
2099/// Cascades ref.test on scratch to produce a JS-typeof-style tag string:
2100/// undefined, null, string, number, boolean, function, then array, which typeof
2101/// would call "object", then the "object" fallthrough.
2102pub(super) fn emit_runtime_type_string(
2103    emitter: &mut FunctionEmitter,
2104    ctx: &CodegenCtx,
2105    scratch: u32,
2106) {
2107    ctx.latch(emit_runtime_type_string_checked(emitter, ctx, scratch));
2108}
2109
2110fn emit_runtime_type_string_checked(
2111    emitter: &mut FunctionEmitter,
2112    ctx: &CodegenCtx,
2113    scratch: u32,
2114) -> Result<(), crate::compiler_error::CompilerFailure> {
2115    let string_type_idx = ctx
2116        .symbols
2117        .string_type_idx()
2118        .ok_or_else(|| crate::codegen::internal_failure("$string intrinsic registered"))?;
2119    let string_ref = ValType::Ref(RefType {
2120        nullable: false,
2121        heap_type: HeapType::Concrete(string_type_idx),
2122    });
2123    let block = BlockType::Result(string_ref);
2124
2125    emitter.instruction(Instruction::LocalGet(scratch));
2126    crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
2127    emitter.emit_if(block);
2128    emit_type_tag(emitter, ctx, "undefined")?;
2129    emitter.emit_else();
2130
2131    // null gets its own arm rather than JS's "object" tag — clearer cast error messages.
2132    emitter.instruction(Instruction::LocalGet(scratch));
2133    emitter.instruction(Instruction::RefIsNull);
2134    emitter.emit_if(block);
2135    emit_type_tag(emitter, ctx, "null")?;
2136    emitter.emit_else();
2137
2138    emitter.instruction(Instruction::LocalGet(scratch));
2139    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(
2140        string_type_idx,
2141    )));
2142    emitter.emit_if(block);
2143    emit_type_tag(emitter, ctx, "string")?;
2144    emitter.emit_else();
2145
2146    let num_idx = ctx
2147        .symbols
2148        .boxed_number_type_idx()
2149        .ok_or_else(|| crate::codegen::internal_failure("$BoxedNumber intrinsic registered"))?;
2150    emitter.instruction(Instruction::LocalGet(scratch));
2151    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(num_idx)));
2152    emitter.emit_if(block);
2153    emit_type_tag(emitter, ctx, "number")?;
2154    emitter.emit_else();
2155
2156    let bool_idx = ctx
2157        .symbols
2158        .boxed_boolean_type_idx()
2159        .ok_or_else(|| crate::codegen::internal_failure("$BoxedBoolean intrinsic registered"))?;
2160    emitter.instruction(Instruction::LocalGet(scratch));
2161    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(bool_idx)));
2162    emitter.emit_if(block);
2163    emit_type_tag(emitter, ctx, "boolean")?;
2164    emitter.emit_else();
2165
2166    let closure_idx = ctx
2167        .symbols
2168        .closure_type_idx()
2169        .ok_or_else(|| crate::codegen::internal_failure("$Closure intrinsic registered"))?;
2170    emitter.instruction(Instruction::LocalGet(scratch));
2171    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(closure_idx)));
2172    emitter.emit_if(block);
2173    emit_type_tag(emitter, ctx, "function")?;
2174    emitter.emit_else();
2175
2176    // Arrays too, so `got object` never answers an expected array or tuple.
2177    let array_idx = ctx
2178        .symbols
2179        .intrinsic_type_indices()
2180        .ok_or_else(|| crate::codegen::internal_failure("intrinsic types registered"))?
2181        .array;
2182    emitter.instruction(Instruction::LocalGet(scratch));
2183    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(array_idx)));
2184    emitter.emit_if(block);
2185    emit_type_tag(emitter, ctx, "array")?;
2186    emitter.emit_else();
2187
2188    emit_type_tag(emitter, ctx, "object")?;
2189
2190    for _ in 0..6 {
2191        emitter.emit_end();
2192    }
2193
2194    emitter.emit_end();
2195    Ok(())
2196}
2197
2198/// Whether `ty` spells `undefined`. A required `unknown` field still needs its key.
2199fn admits_undefined(ty: &Type) -> bool {
2200    match ty.lowered() {
2201        Type::Undefined | Type::Void => true,
2202        Type::Union(members) => members.iter().any(admits_undefined),
2203        _ => false,
2204    }
2205}
2206
2207/// A tag the string pool interned is read from its data segment, which is
2208/// smaller than spelling it out at each of the many failure paths that name it.
2209fn emit_type_tag(
2210    emitter: &mut FunctionEmitter,
2211    ctx: &CodegenCtx,
2212    tag: &str,
2213) -> Result<(), crate::compiler_error::CompilerFailure> {
2214    match ctx.strings.lookup_text(tag) {
2215        Some(pool_idx) => {
2216            crate::codegen::function_emitter::emit_pooled_string(emitter, ctx, pool_idx)
2217        }
2218        None => emit_inline_string_checked(emitter, ctx, tag),
2219    }
2220}
2221
2222/// Visited entries pair the value with its effective type arguments. Forwarded
2223/// descriptors preserve identity; growing arguments construct fresh ones.
2224fn emit_same_validator_environment(
2225    emitter: &mut FunctionEmitter,
2226    ctx: &CodegenCtx,
2227    index: u32,
2228    count: usize,
2229) -> Result<(), crate::compiler_error::CompilerFailure> {
2230    emitter.instruction(Instruction::I32Const(1));
2231    if count == 0 {
2232        return Ok(());
2233    }
2234    let intr = ctx
2235        .symbols
2236        .intrinsic_type_indices()
2237        .ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?;
2238    let closure = ctx
2239        .symbols
2240        .closure_struct_type_idx(super::field_guards::signature())
2241        .ok_or_else(|| crate::codegen::internal_failure("predicate closure type"))?;
2242    for slot in 0..count {
2243        emitter.instruction(Instruction::LocalGet(1));
2244        emitter.instruction(Instruction::LocalGet(index));
2245        emitter.instruction(Instruction::I32Const(2));
2246        emitter.instruction(Instruction::I32Mul);
2247        emitter.instruction(Instruction::I32Const(1));
2248        emitter.instruction(Instruction::I32Add);
2249        emitter.instruction(Instruction::ArrayGet(intr.raw_array));
2250        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(closure)));
2251        emitter.instruction(Instruction::StructGet {
2252            struct_type_index: closure,
2253            field_index: 2,
2254        });
2255        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
2256            intr.object_fields,
2257        )));
2258        emitter.instruction(Instruction::I32Const(slot as i32));
2259        emitter.instruction(Instruction::ArrayGet(intr.object_fields));
2260        emitter.instruction(Instruction::LocalGet(4));
2261        emitter.instruction(Instruction::I32Const(slot as i32));
2262        emitter.instruction(Instruction::ArrayGet(intr.object_fields));
2263        emitter.instruction(Instruction::RefEq);
2264        emitter.instruction(Instruction::I32And);
2265    }
2266    Ok(())
2267}
2268
2269fn emit_store_validator_environment(
2270    emitter: &mut FunctionEmitter,
2271    ctx: &CodegenCtx,
2272) -> Result<(), crate::compiler_error::CompilerFailure> {
2273    let intr = ctx
2274        .symbols
2275        .intrinsic_type_indices()
2276        .ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?;
2277    let closure = ctx
2278        .symbols
2279        .closure_struct_type_idx(super::field_guards::signature())
2280        .ok_or_else(|| crate::codegen::internal_failure("predicate closure type"))?;
2281    emitter.instruction(Instruction::LocalGet(1));
2282    emitter.instruction(Instruction::LocalGet(3));
2283    emitter.instruction(Instruction::I32Const(2));
2284    emitter.instruction(Instruction::I32Mul);
2285    emitter.instruction(Instruction::I32Const(1));
2286    emitter.instruction(Instruction::I32Add);
2287    emitter.instruction(Instruction::GlobalGet(
2288        ctx.symbols
2289            .closure_vtable_global_idx()
2290            .ok_or_else(|| crate::codegen::internal_failure("closure vtable"))?,
2291    ));
2292    emitter.instruction(Instruction::RefFunc(
2293        *ctx.symbols
2294            .type_descriptor_functions
2295            .get(&Type::Unknown)
2296            .ok_or_else(|| {
2297                crate::codegen::internal_failure(
2298                    "validator environment descriptor is not registered",
2299                )
2300            })?,
2301    ));
2302    emitter.instruction(Instruction::LocalGet(4));
2303    emitter.instruction(Instruction::I64Const(0));
2304    emitter.instruction(Instruction::StructNew(closure));
2305    emitter.instruction(Instruction::ArraySet(intr.raw_array));
2306
2307    Ok(())
2308}
2309
2310/// Tests each element of the array in `value_local`. When the array holds a
2311/// record's values, `record_keys` names a failing element by its key instead of
2312/// its index.
2313fn emit_elements_test(
2314    emitter: &mut FunctionEmitter,
2315    ctx: &CodegenCtx,
2316    value_local: u32,
2317    elem: &Type,
2318    interface_stack: &mut std::collections::BTreeSet<Type>,
2319    validator_state: Option<ValidatorState>,
2320    record_keys: Option<diagnostic::RecordKeys>,
2321) -> Result<(), crate::compiler_error::CompilerFailure> {
2322    let i32_block = BlockType::Result(ValType::I32);
2323    let intr = ctx
2324        .symbols
2325        .intrinsic_type_indices()
2326        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
2327    let array_idx = ctx
2328        .symbols
2329        .array_type_idx()
2330        .ok_or_else(|| crate::codegen::internal_failure("$Array intrinsic registered"))?;
2331    emitter.instruction(Instruction::LocalGet(value_local));
2332    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(array_idx)));
2333    emitter.emit_if(i32_block);
2334    let raw_local = emitter.add_anonymous_local(ValType::Ref(RefType {
2335        nullable: false,
2336        heap_type: HeapType::Concrete(intr.raw_array),
2337    }))?;
2338    emitter.instruction(Instruction::LocalGet(value_local));
2339    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(array_idx)));
2340    emitter.instruction(Instruction::StructGet {
2341        struct_type_index: array_idx,
2342        field_index: 1,
2343    });
2344    emitter.instruction(Instruction::LocalSet(raw_local));
2345    let acc = emitter.add_anonymous_local(ValType::I32)?;
2346    let i = emitter.add_anonymous_local(ValType::I32)?;
2347    let len = emitter.add_anonymous_local(ValType::I32)?;
2348    let elem_local = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
2349    emitter.instruction(Instruction::I32Const(1));
2350    emitter.instruction(Instruction::LocalSet(acc));
2351    emitter.instruction(Instruction::I32Const(0));
2352    emitter.instruction(Instruction::LocalSet(i));
2353    emitter.instruction(Instruction::LocalGet(value_local));
2354    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(array_idx)));
2355    emitter.instruction(Instruction::StructGet {
2356        struct_type_index: array_idx,
2357        field_index: 2,
2358    });
2359    emitter.instruction(Instruction::LocalSet(len));
2360    emitter.emit_block(BlockType::Empty);
2361    emitter.emit_loop(BlockType::Empty);
2362    emitter.instruction(Instruction::LocalGet(i));
2363    emitter.instruction(Instruction::LocalGet(len));
2364    emitter.instruction(Instruction::I32GeU);
2365    emitter.instruction(Instruction::BrIf(1));
2366    emitter.instruction(Instruction::LocalGet(raw_local));
2367    emitter.instruction(Instruction::LocalGet(i));
2368    emitter.instruction(Instruction::ArrayGet(intr.raw_array));
2369    emitter.instruction(Instruction::LocalSet(elem_local));
2370    emitter.instruction(Instruction::LocalGet(acc));
2371    let parent_path = match record_keys {
2372        Some(keys) => diagnostic::record_key(emitter, keys, i),
2373        None => diagnostic::index(emitter, ctx, i),
2374    };
2375    emit_structural_test_inner(
2376        emitter,
2377        ctx,
2378        elem_local,
2379        elem,
2380        interface_stack,
2381        validator_state,
2382    )?;
2383    diagnostic::pop(emitter, parent_path);
2384    emitter.instruction(Instruction::I32And);
2385    emitter.instruction(Instruction::LocalSet(acc));
2386    emitter.instruction(Instruction::LocalGet(i));
2387    emitter.instruction(Instruction::I32Const(1));
2388    emitter.instruction(Instruction::I32Add);
2389    emitter.instruction(Instruction::LocalSet(i));
2390    emitter.instruction(Instruction::Br(0));
2391    emitter.emit_end();
2392    emitter.emit_end();
2393    emitter.instruction(Instruction::LocalGet(acc));
2394    emitter.emit_else();
2395    emitter.instruction(Instruction::I32Const(0));
2396    emitter.emit_end();
2397    Ok(())
2398}
2399
2400fn emit_index_conformance(
2401    emitter: &mut FunctionEmitter,
2402    ctx: &CodegenCtx,
2403    object: u32,
2404    index: &crate::IndexSignature,
2405    interface_stack: &mut std::collections::BTreeSet<Type>,
2406    validator_state: Option<ValidatorState>,
2407) -> Result<(), crate::compiler_error::CompilerFailure> {
2408    let domain = serde_json::to_string(&crate::property_keys::runtime_domain(&index.key).map_err(
2409        |error| error.into_failure(crate::compiler_error::CompilerStage::Codegen, None),
2410    )?)
2411    .map_err(|error| {
2412        crate::codegen::internal_failure(format!("could not encode index key domain: {error}"))
2413    })?;
2414    emit_inline_string_checked(emitter, ctx, &domain)?;
2415    let domain_local = emitter.add_anonymous_local(ValType::Ref(wasm_encoder::RefType {
2416        nullable: false,
2417        heap_type: HeapType::Concrete(string_idx(ctx)?),
2418    }))?;
2419    emitter.instruction(Instruction::LocalSet(domain_local));
2420    emitter.instruction(Instruction::LocalGet(object));
2421    let Some(symbol) = ctx.require(
2422        ctx.symbols
2423            .prelude_func_idx("ObjectConstructor##recordValues"),
2424        "record validator imported",
2425    ) else {
2426        return Ok(());
2427    };
2428    emitter.instruction(Instruction::LocalGet(domain_local));
2429    emitter.instruction(Instruction::Call(symbol));
2430    let values = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
2431    emitter.instruction(Instruction::LocalSet(values));
2432    let record_keys = diagnostic::record_keys(emitter, ctx, object, values, domain_local);
2433    emit_elements_test(
2434        emitter,
2435        ctx,
2436        values,
2437        &index.value,
2438        interface_stack,
2439        validator_state,
2440        record_keys,
2441    )?;
2442    Ok(())
2443}
2444
2445#[cfg(test)]
2446mod representation_invariant_tests {
2447    use super::*;
2448    use crate::codegen::invariant_tests::{assert_internal, with_context};
2449
2450    #[test]
2451    fn primitive_representation_test_returns_internal_failure() {
2452        with_context(
2453            &crate::TypedAst::new(),
2454            &crate::codegen::SymbolTable::default(),
2455            |ctx| {
2456                let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
2457                assert_internal(
2458                    emit_representation_test(&mut emitter, ctx, 0, &Type::Number).unwrap_err(),
2459                );
2460            },
2461        );
2462    }
2463}