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