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