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    let fit = |emitter: &mut FunctionEmitter| {
702        super::closure_coercions::emit_defaults_fit(emitter, ctx, value_local, target)
703    };
704    emit_ref_test(emitter, value_local, closure_struct);
705    emitter.emit_if(BlockType::Result(ValType::I32));
706    super::closure_coercions::emit_may_have_argument_metadata(
707        emitter,
708        ctx,
709        value_local,
710        closure_struct,
711    )?;
712    emitter.emit_if(BlockType::Result(ValType::I32));
713    fit(emitter)?;
714    emitter.emit_else();
715    emitter.instruction(Instruction::I32Const(i32::from(!has_rest)));
716    emitter.emit_end();
717    emitter.emit_else();
718    if has_rest {
719        emitter.instruction(Instruction::I32Const(0));
720    } else {
721        fit(emitter)?;
722    }
723    emitter.emit_end();
724    Ok(())
725}
726
727fn emit_interface_ref_test(
728    emitter: &mut FunctionEmitter,
729    ctx: &CodegenCtx,
730    value_local: u32,
731    ty: &Type,
732    interface_stack: &mut std::collections::BTreeSet<Type>,
733    validator_state: Option<ValidatorState>,
734) -> Result<(), crate::compiler_error::CompilerFailure> {
735    let key = ty.peel().clone();
736    if ctx.symbols.generic_runtime_validator(&key).is_some() {
737        emit_validator_or_representation(emitter, ctx, value_local, ty, &key, validator_state)?;
738        return Ok(());
739    }
740    let _: () = match ctx.ta.runtime_type_tests.get(&key) {
741        Some(crate::FieldNarrowingTest::Shape(shape)) => emit_structural_test_inner(
742            emitter,
743            ctx,
744            value_local,
745            shape,
746            interface_stack,
747            validator_state,
748        )?,
749        Some(crate::FieldNarrowingTest::Interface(test)) if interface_stack.insert(key.clone()) => {
750            emit_interface_test_inner(
751                emitter,
752                ctx,
753                value_local,
754                test,
755                interface_stack,
756                validator_state,
757            )?;
758            interface_stack.remove(&key);
759        }
760        _ => {
761            emit_validator_or_representation(emitter, ctx, value_local, ty, &key, validator_state)?;
762        }
763    };
764    Ok(())
765}
766
767fn emit_validator_or_representation(
768    emitter: &mut FunctionEmitter,
769    ctx: &CodegenCtx,
770    value_local: u32,
771    ty: &Type,
772    key: &Type,
773    validator_state: Option<ValidatorState>,
774) -> Result<(), crate::compiler_error::CompilerFailure> {
775    if let Some(func_idx) = ctx.symbols.runtime_validator_idx(key) {
776        emitter.instruction(Instruction::LocalGet(value_local));
777        let visited = emit_validator_state(emitter, ctx, validator_state)?;
778        super::runtime_descriptors::validator_environment(emitter, ctx, key)?;
779        emitter.instruction(Instruction::Call(func_idx));
780        diagnostic::load_recursive(emitter, ctx, visited);
781    } else {
782        emit_representation_test(emitter, ctx, value_local, ty)?;
783    };
784    Ok(())
785}
786
787/// Validate an erased value against its declared target before casting it.
788/// Mismatches become a catchable `TypeError` instead of an unchecked Wasm trap.
789pub(crate) fn emit_checked_cast_on_stack(
790    emitter: &mut FunctionEmitter,
791    ctx: &CodegenCtx,
792    source_ty: &Type,
793    target_ty: &Type,
794) -> Result<(), crate::compiler_error::CompilerFailure> {
795    cast::emit_box(emitter, ctx, source_ty)?;
796    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
797    emitter.instruction(Instruction::LocalSet(scratch));
798    let previous_diagnostic = emitter.cast_diagnostic.take();
799    if diagnostic::has_nested_paths(target_ty) {
800        diagnostic::begin(emitter, ctx);
801    }
802    match ctx.ta.runtime_type_tests.get(target_ty.peel()) {
803        Some(crate::FieldNarrowingTest::Interface(test)) => {
804            emit_interface_test(emitter, ctx, scratch, target_ty, test)?;
805        }
806        Some(crate::FieldNarrowingTest::Shape(shape)) => {
807            emit_structural_test(emitter, ctx, scratch, shape, target_ty)?;
808        }
809        _ => emit_target_test(emitter, ctx, scratch, target_ty)?,
810    }
811    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
812    emitter.instruction(Instruction::LocalGet(scratch));
813    cast::emit_cast_to(emitter, ctx, target_ty)?;
814    emitter.emit_else();
815    emit_cast_throw(emitter, ctx, scratch, target_ty);
816    emitter.emit_end();
817    emitter.cast_diagnostic = previous_diagnostic;
818    Ok(())
819}
820
821/// Validate an erased parameter before rebinding it to the declaration's
822/// physical slot. Interface values may have host-owned carriers that cannot be
823/// reconstructed structurally here (for example Temporal plain-date structs),
824/// so parameters require representation safety; concrete value types retain
825/// the stronger structural check.
826pub(crate) fn emit_checked_parameter_cast_on_stack(
827    emitter: &mut FunctionEmitter,
828    ctx: &CodegenCtx,
829    source_ty: &Type,
830    target_ty: &Type,
831) -> Result<(), crate::compiler_error::CompilerFailure> {
832    if !is_interface_parameter(target_ty) {
833        emit_checked_cast_on_stack(emitter, ctx, source_ty, target_ty)?;
834        return Ok(());
835    }
836
837    cast::emit_box(emitter, ctx, source_ty)?;
838    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
839    emitter.instruction(Instruction::LocalSet(scratch));
840    emit_representation_test(emitter, ctx, scratch, target_ty)?;
841    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
842    emitter.instruction(Instruction::LocalGet(scratch));
843    cast::emit_cast_to(emitter, ctx, target_ty)?;
844    emitter.emit_else();
845    emit_cast_throw(emitter, ctx, scratch, target_ty);
846    emitter.emit_end();
847    Ok(())
848}
849
850fn is_interface_parameter(ty: &Type) -> bool {
851    match ty.peel() {
852        Type::InterfaceRef { .. } => true,
853        Type::Union(members) => {
854            let mut non_null = members
855                .iter()
856                .filter(|member| !matches!(member.peel(), Type::Null | Type::Undefined));
857            non_null
858                .next()
859                .is_some_and(|member| matches!(member.peel(), Type::InterfaceRef { .. }))
860                && non_null.next().is_none()
861        }
862        _ => false,
863    }
864}
865
866/// Require the carrier an operation consumes, without validating its contents
867/// or invoking getters. Reading a value itself does not perform this check.
868pub(crate) fn emit_operation_cast_on_stack(
869    emitter: &mut FunctionEmitter,
870    ctx: &CodegenCtx,
871    source_ty: &Type,
872    target_ty: &Type,
873) -> Result<(), crate::compiler_error::CompilerFailure> {
874    cast::emit_box(emitter, ctx, source_ty)?;
875    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
876    emitter.instruction(Instruction::LocalSet(scratch));
877    // Operation checks validate only the carrier, so they have no nested path.
878    let previous_diagnostic = emitter.cast_diagnostic.take();
879    if matches!(target_ty.peel(), Type::Function { .. }) {
880        let signature = crate::codegen::closures::classify(target_ty.peel())?;
881        emit_representation_test(emitter, ctx, scratch, target_ty)?;
882        let opposite = crate::codegen::closures::ClosureSig {
883            is_void: !signature.is_void,
884            ..signature
885        };
886        if let Some(structure) = ctx.symbols.closure_struct_type_idx(opposite) {
887            emit_ref_test(emitter, scratch, structure);
888            emitter.instruction(Instruction::I32Or);
889        }
890        if matches!(
891            target_ty.peel(),
892            Type::Function {
893                has_rest: false,
894                ..
895            }
896        ) {
897            emitter.emit_if(BlockType::Result(ValType::I32));
898            emitter.instruction(Instruction::I32Const(1));
899            emitter.emit_else();
900            super::closure_coercions::emit_defaults_fit(
901                emitter,
902                ctx,
903                scratch,
904                super::closure_coercions::DefaultsFitTarget::any_convention(signature.arity),
905            )?;
906            emitter.emit_end();
907        }
908    } else {
909        emit_target_test(emitter, ctx, scratch, target_ty)?;
910    }
911    emitter.emit_if(BlockType::Result(ctx.symbols.value_type(target_ty)?));
912    emitter.instruction(Instruction::LocalGet(scratch));
913    cast::emit_cast_to(emitter, ctx, target_ty)?;
914    emitter.emit_else();
915    emit_cast_throw(emitter, ctx, scratch, target_ty);
916    emitter.emit_end();
917    emitter.cast_diagnostic = previous_diagnostic;
918    Ok(())
919}
920
921/// Pushes whether `value_local` holds a `target_ty`, for a target without a
922/// recorded runtime test. A primitive slot (`1 | 2` is an `f64`) has no
923/// reference shape to test, so its values are told apart structurally.
924fn emit_target_test(
925    emitter: &mut FunctionEmitter,
926    ctx: &CodegenCtx,
927    value_local: u32,
928    target_ty: &Type,
929) -> Result<(), crate::compiler_error::CompilerFailure> {
930    if matches!(
931        ctx.symbols.value_type(target_ty)?,
932        ValType::F64 | ValType::I32
933    ) {
934        emit_structural_test(emitter, ctx, value_local, target_ty, target_ty)
935    } else {
936        emit_representation_test(emitter, ctx, value_local, target_ty)
937    }
938}
939
940/// The conservative fallback for types without a full structural validator.
941/// It cannot distinguish two interface values sharing `$Object`, but it proves
942/// the following representation cast safe and turns representation mismatches
943/// into a catchable `TypeError` rather than a Wasm trap.
944fn emit_representation_test(
945    emitter: &mut FunctionEmitter,
946    ctx: &CodegenCtx,
947    value_local: u32,
948    target_ty: &Type,
949) -> Result<(), crate::compiler_error::CompilerFailure> {
950    let ValType::Ref(target) = ctx.symbols.value_type(target_ty)? else {
951        return Err(crate::codegen::internal_failure(
952            "representation test requires a reference type",
953        ));
954    };
955    let allows_null = cast::target_allows_null(ctx, target_ty)?;
956    if allows_null {
957        emitter.instruction(Instruction::LocalGet(value_local));
958        emitter.instruction(Instruction::RefIsNull);
959        emitter.emit_if(BlockType::Result(ValType::I32));
960        emitter.instruction(Instruction::I32Const(1));
961        emitter.emit_else();
962    }
963    emitter.instruction(Instruction::LocalGet(value_local));
964    emitter.instruction(Instruction::RefTestNonNull(target.heap_type));
965    if allows_null {
966        emitter.emit_end();
967    };
968    Ok(())
969}
970
971/// The read of a class field whose declaration narrows an inherited one. The
972/// slot is shared with the parent's declaration, so it can hold a value this
973/// declaration does not admit (spec.md §Classes) — test before casting and throw
974/// a named, catchable `TypeError` instead of letting `ref.cast` raise a bare
975/// `cast failure`. The raw payload slot value is on the stack.
976///
977/// Pushes i32 1 if the object in `obj_local` carries property `fname` at a value
978/// conforming to `field`, 0 otherwise.
979///
980/// A data slot is read and tested. No data slot is not the same as "absent": an
981/// accessor-backed property has no slot, and the value it would answer with is
982/// behind a getter this must not call — a conformance predicate that runs user
983/// code could throw or have side effects. The accessor slot's existence is
984/// therefore accepted here; the accessor call validates its erased return at
985/// the actual read. Neither slot means the property really is missing, and only
986/// an optional field tolerates that.
987///
988/// The read counterpart is `expr::emit_object_property_read`, which makes the
989/// same three-way distinction and calls the getter because it needs the value.
990fn emit_field_conformance(
991    emitter: &mut FunctionEmitter,
992    ctx: &CodegenCtx,
993    locals: FieldConformanceLocals,
994    fname: &str,
995    field: &crate::ObjectField,
996    interface_stack: &mut std::collections::BTreeSet<Type>,
997    validator_state: Option<ValidatorState>,
998) -> Result<(), crate::compiler_error::CompilerFailure> {
999    let checkpoint = diagnostic::checkpoint(emitter, ctx);
1000    let parent_path = diagnostic::field(emitter, ctx, fname);
1001    let i32_block = BlockType::Result(ValType::I32);
1002    let intrinsics = ctx
1003        .symbols
1004        .intrinsic_type_indices()
1005        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1006    let name_global = ctx
1007        .symbols
1008        .field_name_string_global_idx(fname)
1009        .ok_or_else(|| {
1010            crate::codegen::internal_failure(
1011                "per-name string global recorded for cast-target field",
1012            )
1013        })?;
1014    let index_local = emitter.add_anonymous_local(ValType::I32)?;
1015    crate::codegen::function_emitter::expr::emit_object_field_index_by_name(
1016        emitter,
1017        ctx,
1018        locals.object,
1019        name_global,
1020    );
1021    emitter.instruction(Instruction::LocalTee(index_local));
1022    emitter.instruction(Instruction::I32Const(0));
1023    emitter.instruction(Instruction::I32GeS);
1024    emitter.emit_if(i32_block);
1025    crate::codegen::function_emitter::expr::emit_field_slot_get(
1026        emitter,
1027        intrinsics,
1028        locals.object,
1029        index_local,
1030    );
1031    emitter.instruction(Instruction::LocalSet(locals.field));
1032    emit_structural_test_inner(
1033        emitter,
1034        ctx,
1035        locals.field,
1036        &field.ty,
1037        interface_stack,
1038        validator_state,
1039    )?;
1040    // A missing key reads as `undefined`, so a field that admits it conforms
1041    // without one: `{ parent: number | undefined }` round-trips through JSON.
1042    let may_be_absent = field.optional || admits_undefined(&field.ty);
1043    if may_be_absent {
1044        emitter.instruction(Instruction::LocalGet(locals.field));
1045        crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
1046        emitter.instruction(Instruction::I32Or);
1047    } else {
1048        // A required undefined-valued property must still exist on the object.
1049        emitter.instruction(Instruction::LocalGet(locals.object));
1050        emitter.instruction(Instruction::StructGet {
1051            struct_type_index: intrinsics.object_shape,
1052            field_index: 1,
1053        });
1054        emitter.instruction(Instruction::LocalGet(index_local));
1055        emitter.instruction(Instruction::ArrayGet(intrinsics.field_names));
1056        crate::codegen::field_names::emit_name_presence(emitter, ctx)?;
1057        emitter.instruction(Instruction::LocalGet(locals.field));
1058        crate::codegen::function_emitter::expr::emit_is_nullish(emitter, ctx)?;
1059        emitter.instruction(Instruction::I32Eqz);
1060        emitter.instruction(Instruction::I32Or);
1061        emitter.instruction(Instruction::I32And);
1062    }
1063    emitter.emit_else();
1064    let getter = crate::codegen::classes::accessor_getter_name(fname);
1065    if may_be_absent {
1066        // Absent and accessor-backed both conform, so the branch is constant.
1067        emitter.instruction(Instruction::I32Const(1));
1068    } else if crate::codegen::function_emitter::expr::accessor_branch_emittable(ctx, &getter) {
1069        let _ = crate::codegen::function_emitter::expr::emit_is_accessor_backed(
1070            emitter,
1071            ctx,
1072            locals.object,
1073            &getter,
1074            crate::AccessorKind::Get,
1075        )?;
1076    } else {
1077        emitter.instruction(Instruction::I32Const(0));
1078    }
1079    emitter.emit_end();
1080    diagnostic::finish(emitter, ctx, checkpoint)?;
1081    diagnostic::pop(emitter, parent_path);
1082    Ok(())
1083}
1084
1085pub fn emit_narrowed_field_read(
1086    emitter: &mut FunctionEmitter,
1087    ctx: &CodegenCtx,
1088    check: &crate::FieldNarrowingCheck,
1089    result_ty: &Type,
1090) -> Result<(), crate::compiler_error::CompilerFailure> {
1091    emit_narrowed_field_read_as(emitter, ctx, check, result_ty, result_ty)?;
1092    Ok(())
1093}
1094
1095pub(crate) fn emit_narrowed_field_read_as(
1096    emitter: &mut FunctionEmitter,
1097    ctx: &CodegenCtx,
1098    check: &crate::FieldNarrowingCheck,
1099    result_ty: &Type,
1100    check_ty: &Type,
1101) -> Result<(), crate::compiler_error::CompilerFailure> {
1102    let result_val = ctx.symbols.value_type(result_ty)?;
1103    let scratch = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
1104    emitter.instruction(Instruction::LocalSet(scratch));
1105    // Prefer the concrete read-time descriptor. It includes generic
1106    // substitutions and nested interface metadata that the declaration could
1107    // not know; imported legacy declarations fall back to their recorded test.
1108    let runtime_test = ctx.ta.runtime_type_tests.get(check_ty.peel());
1109    let test = if check
1110        .minimal_test_target
1111        .as_ref()
1112        .is_some_and(|target| target.peel() == check_ty.peel())
1113    {
1114        if matches!(check.test, crate::FieldNarrowingTest::NonNull) {
1115            &check.test
1116        } else {
1117            &crate::FieldNarrowingTest::Representation
1118        }
1119    } else {
1120        match runtime_test {
1121            Some(crate::FieldNarrowingTest::Representation)
1122                if !matches!(&check.test, crate::FieldNarrowingTest::Representation) =>
1123            {
1124                &check.test
1125            }
1126            Some(test) => test,
1127            None => &check.test,
1128        }
1129    };
1130    // Representation and null checks cannot descend to a nested location.
1131    // Keep ordinary property reads free of unused diagnostic state.
1132    let previous_diagnostic = emitter.cast_diagnostic.take();
1133    let nested_check = match test {
1134        crate::FieldNarrowingTest::Shape(ty) => diagnostic::has_nested_paths(ty),
1135        crate::FieldNarrowingTest::Interface(_) => true,
1136        _ => false,
1137    };
1138    if nested_check {
1139        diagnostic::begin(emitter, ctx);
1140    }
1141    match test {
1142        // Presence is the check only where the read's type rejects `null`. On a
1143        // generic class the declaration cannot say: `Sub<T>`'s `v: T` is
1144        // `string | null` at `Sub<string | null>`, where a `null` is legal and
1145        // the cast lets it through anyway. `result_ty` is substituted, so it
1146        // answers what the declaration could not.
1147        crate::FieldNarrowingTest::NonNull => {
1148            if cast::target_allows_null(ctx, check_ty)? {
1149                emitter.instruction(Instruction::I32Const(1));
1150            } else {
1151                emit_is_non_null(emitter, scratch);
1152            }
1153        }
1154        crate::FieldNarrowingTest::Shape(shape) => {
1155            emit_structural_test(emitter, ctx, scratch, shape, check_ty)?;
1156            // `emit_cast_to` lifts to non-null for every target it does not admit
1157            // a null for, so those are exactly the targets a `null` in the slot
1158            // would trap on.
1159            if !cast::target_allows_null(ctx, check_ty)? {
1160                emit_is_non_null(emitter, scratch);
1161                emitter.instruction(Instruction::I32And);
1162            }
1163        }
1164        crate::FieldNarrowingTest::Interface(test) => {
1165            emit_interface_test(emitter, ctx, scratch, check_ty, test)?;
1166        }
1167        crate::FieldNarrowingTest::Substituted => {
1168            emit_representation_test(emitter, ctx, scratch, check_ty)?;
1169        }
1170        crate::FieldNarrowingTest::Representation => {
1171            emit_representation_test(emitter, ctx, scratch, check_ty)?;
1172        }
1173    }
1174    emitter.emit_if(BlockType::Result(result_val));
1175    emitter.instruction(Instruction::LocalGet(scratch));
1176    cast::emit_cast_to(emitter, ctx, result_ty)?;
1177    emitter.emit_else();
1178    emit_inline_string(emitter, ctx, &check.message);
1179    diagnostic::append_failure(emitter, ctx);
1180    emit_type_error_from_message(emitter, ctx);
1181    emitter.emit_end();
1182    emitter.cast_diagnostic = previous_diagnostic;
1183    Ok(())
1184}
1185
1186/// `ty` is the type `test` checks, named if the check exhausts its step limit.
1187fn emit_interface_test(
1188    emitter: &mut FunctionEmitter,
1189    ctx: &CodegenCtx,
1190    value_local: u32,
1191    ty: &Type,
1192    test: &crate::InterfaceNarrowingTest,
1193) -> Result<(), crate::compiler_error::CompilerFailure> {
1194    ctx.checking(ty, || {
1195        emit_interface_test_inner(
1196            emitter,
1197            ctx,
1198            value_local,
1199            test,
1200            &mut std::collections::BTreeSet::new(),
1201            None,
1202        )?;
1203        ctx.check_function_limits(emitter)
1204    })
1205}
1206
1207fn emit_interface_test_inner(
1208    emitter: &mut FunctionEmitter,
1209    ctx: &CodegenCtx,
1210    value_local: u32,
1211    test: &crate::InterfaceNarrowingTest,
1212    interface_stack: &mut std::collections::BTreeSet<Type>,
1213    validator_state: Option<ValidatorState>,
1214) -> Result<(), crate::compiler_error::CompilerFailure> {
1215    ctx.charge_validator_step(emitter)?;
1216    let checkpoint = diagnostic::checkpoint(emitter, ctx);
1217    if test.nullable || test.undefined {
1218        emitter.instruction(Instruction::LocalGet(value_local));
1219        if test.nullable && test.undefined {
1220            crate::codegen::function_emitter::expr::emit_is_nullish(emitter, ctx)?;
1221        } else if test.undefined {
1222            crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
1223        } else {
1224            emitter.instruction(Instruction::RefIsNull);
1225        }
1226        emitter.emit_if(BlockType::Result(ValType::I32));
1227        emitter.instruction(Instruction::I32Const(1));
1228        emitter.emit_else();
1229    }
1230
1231    if !test.shape_allowed {
1232        emit_non_shape_interface_test(emitter, ctx, value_local, test, validator_state)?;
1233        if test.nullable || test.undefined {
1234            emitter.emit_end();
1235        }
1236        return Ok(());
1237    }
1238
1239    let shape_idx = ctx
1240        .symbols
1241        .object_shape_type_idx()
1242        .ok_or_else(|| crate::codegen::internal_failure("$ObjectShape intrinsic registered"))?;
1243    emitter.instruction(Instruction::LocalGet(value_local));
1244    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(shape_idx)));
1245    emitter.emit_if(BlockType::Result(ValType::I32));
1246    let object_local = emitter.add_anonymous_local(ValType::Ref(RefType {
1247        nullable: false,
1248        heap_type: HeapType::Concrete(shape_idx),
1249    }))?;
1250    emitter.instruction(Instruction::LocalGet(value_local));
1251    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(shape_idx)));
1252    emitter.instruction(Instruction::LocalSet(object_local));
1253    let field_local = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
1254    emitter.instruction(Instruction::I32Const(1));
1255    for (name, field) in &test.members {
1256        if test.methods.contains(name) {
1257            let parent_path = diagnostic::field(emitter, ctx, name);
1258            let name_global = ctx
1259                .symbols
1260                .field_name_string_global_idx(name)
1261                .ok_or_else(|| {
1262                    crate::codegen::internal_failure("interface narrowing method name registered")
1263                })?;
1264            crate::codegen::function_emitter::expr::emit_object_field_read_by_name(
1265                emitter,
1266                ctx,
1267                object_local,
1268                name_global,
1269            );
1270            emitter.instruction(Instruction::LocalSet(field_local));
1271            emit_structural_test_inner(
1272                emitter,
1273                ctx,
1274                field_local,
1275                &field.ty,
1276                interface_stack,
1277                validator_state,
1278            )?;
1279            diagnostic::pop(emitter, parent_path);
1280        } else {
1281            emit_field_conformance(
1282                emitter,
1283                ctx,
1284                FieldConformanceLocals {
1285                    object: object_local,
1286                    field: field_local,
1287                },
1288                name,
1289                field,
1290                interface_stack,
1291                validator_state,
1292            )?;
1293        }
1294        emitter.instruction(Instruction::I32And);
1295    }
1296    if let Some(index) = &test.index {
1297        emit_index_conformance(
1298            emitter,
1299            ctx,
1300            object_local,
1301            index,
1302            interface_stack,
1303            validator_state,
1304        )?;
1305        emitter.instruction(Instruction::I32And);
1306    }
1307    // Some direct-dispatch interfaces intentionally use `$ObjectShape` only as
1308    // an inert receiver carrier (for example TextEncoder/TextDecoder); their
1309    // methods are host imports, not payload slots. Admit those exact carriers
1310    // alongside ordinary structural conformance.
1311    emit_non_shape_interface_test(emitter, ctx, value_local, test, validator_state)?;
1312    emitter.instruction(Instruction::I32Or);
1313    emitter.emit_else();
1314    emit_non_shape_interface_test(emitter, ctx, value_local, test, validator_state)?;
1315    emitter.emit_end();
1316
1317    if test.nullable || test.undefined {
1318        emitter.emit_end();
1319    }
1320    diagnostic::finish(emitter, ctx, checkpoint)?;
1321    Ok(())
1322}
1323
1324fn emit_non_shape_interface_test(
1325    emitter: &mut FunctionEmitter,
1326    ctx: &CodegenCtx,
1327    value_local: u32,
1328    test: &crate::InterfaceNarrowingTest,
1329    validator_state: Option<ValidatorState>,
1330) -> Result<(), crate::compiler_error::CompilerFailure> {
1331    use crate::InterfaceCarrier;
1332
1333    let matches_any = emitter.add_anonymous_local(ValType::I32)?;
1334    emitter.instruction(Instruction::I32Const(0));
1335    emitter.instruction(Instruction::LocalSet(matches_any));
1336    for carrier in &test.non_shape_carriers {
1337        let intr = ctx
1338            .symbols
1339            .intrinsic_type_indices()
1340            .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1341        match carrier {
1342            InterfaceCarrier::ArrayAny => emit_structural_test_inner(
1343                emitter,
1344                ctx,
1345                value_local,
1346                &Type::Array(Box::new(Type::Unknown)),
1347                &mut std::collections::BTreeSet::new(),
1348                validator_state,
1349            )?,
1350            InterfaceCarrier::Array(ty) => {
1351                emit_structural_test_inner(
1352                    emitter,
1353                    ctx,
1354                    value_local,
1355                    ty,
1356                    &mut std::collections::BTreeSet::new(),
1357                    validator_state,
1358                )?;
1359            }
1360            InterfaceCarrier::Number => emit_ref_test(emitter, value_local, boxed_number_idx(ctx)?),
1361            InterfaceCarrier::Boolean => {
1362                emit_ref_test(emitter, value_local, boxed_boolean_idx(ctx)?);
1363            }
1364            InterfaceCarrier::String => emit_ref_test(emitter, value_local, string_idx(ctx)?),
1365            InterfaceCarrier::BigInt => emit_ref_test(emitter, value_local, intr.bigint),
1366            InterfaceCarrier::Uint8Array => emit_ref_test(emitter, value_local, intr.uint8_array),
1367            InterfaceCarrier::MapAny => emit_ref_test(emitter, value_local, intr.map),
1368            InterfaceCarrier::Map(key, value) => {
1369                emit_map_carrier_test(emitter, ctx, value_local, key, value, validator_state)?;
1370            }
1371            InterfaceCarrier::SetAny => emit_ref_test(emitter, value_local, intr.set),
1372            InterfaceCarrier::Set(element) => {
1373                emit_set_carrier_test(emitter, ctx, value_local, element, validator_state)?;
1374            }
1375            InterfaceCarrier::RegExp => emit_ref_test(emitter, value_local, intr.regex),
1376            InterfaceCarrier::RegExpMatch => {
1377                emit_ref_test(emitter, value_local, intr.regex_match_box);
1378            }
1379            InterfaceCarrier::TemporalInstant => {
1380                emit_ref_test(emitter, value_local, intr.temporal_instant);
1381            }
1382            InterfaceCarrier::TemporalDuration => {
1383                emit_ref_test(emitter, value_local, intr.temporal_duration);
1384            }
1385            InterfaceCarrier::TemporalZonedDateTime => {
1386                emit_ref_test(emitter, value_local, intr.temporal_zdt);
1387            }
1388            InterfaceCarrier::TemporalPlainDate => {
1389                emit_ref_test(emitter, value_local, intr.temporal_plain_date);
1390            }
1391            InterfaceCarrier::TemporalPlainTime => {
1392                emit_ref_test(emitter, value_local, intr.temporal_plain_time);
1393            }
1394            InterfaceCarrier::TemporalPlainDateTime => {
1395                emit_ref_test(emitter, value_local, intr.temporal_plain_date_time);
1396            }
1397            InterfaceCarrier::TemporalPlainYearMonth => {
1398                emit_ref_test(emitter, value_local, intr.temporal_plain_year_month);
1399            }
1400            InterfaceCarrier::TemporalPlainMonthDay => {
1401                emit_ref_test(emitter, value_local, intr.temporal_plain_month_day);
1402            }
1403            InterfaceCarrier::ObjectShape => {
1404                emit_ref_test(emitter, value_local, intr.object_shape);
1405            }
1406            InterfaceCarrier::Url => emit_ref_test(emitter, value_local, intr.url),
1407            InterfaceCarrier::FsStat => emit_ref_test(emitter, value_local, intr.fs_stat),
1408            InterfaceCarrier::FsPeek => emit_ref_test(emitter, value_local, intr.fs_peek),
1409            InterfaceCarrier::FsDirEntry => emit_ref_test(emitter, value_local, intr.fs_dir_entry),
1410            InterfaceCarrier::FsInfo => emit_ref_test(emitter, value_local, intr.fs_info),
1411            InterfaceCarrier::FsMountInfo => {
1412                emit_ref_test(emitter, value_local, intr.fs_mount_info);
1413            }
1414            InterfaceCarrier::FsFileWriter => {
1415                emit_ref_test(emitter, value_local, intr.fs_file_writer);
1416            }
1417            InterfaceCarrier::HttpResponse => {
1418                emit_ref_test(emitter, value_local, intr.http_response);
1419            }
1420            InterfaceCarrier::HttpDownloadResult => {
1421                emit_ref_test(emitter, value_local, intr.http_download_result);
1422            }
1423            InterfaceCarrier::SessionEntry => {
1424                emit_ref_test(emitter, value_local, intr.session_entry);
1425            }
1426            InterfaceCarrier::SessionPage => emit_ref_test(emitter, value_local, intr.session_page),
1427        }
1428        emitter.instruction(Instruction::LocalGet(matches_any));
1429        emitter.instruction(Instruction::I32Or);
1430        emitter.instruction(Instruction::LocalSet(matches_any));
1431    }
1432    emitter.instruction(Instruction::LocalGet(matches_any));
1433    Ok(())
1434}
1435
1436fn emit_set_carrier_test(
1437    emitter: &mut FunctionEmitter,
1438    ctx: &CodegenCtx,
1439    value_local: u32,
1440    element: &Type,
1441    validator_state: Option<ValidatorState>,
1442) -> Result<(), crate::compiler_error::CompilerFailure> {
1443    let intr = ctx
1444        .symbols
1445        .intrinsic_type_indices()
1446        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1447    emit_ref_test(emitter, value_local, intr.set);
1448    emitter.emit_if(BlockType::Result(ValType::I32));
1449    let backing = emitter.add_anonymous_local(ValType::Ref(RefType {
1450        nullable: false,
1451        heap_type: HeapType::Concrete(intr.set),
1452    }))?;
1453    let elements = emitter.add_anonymous_local(ValType::Ref(RefType {
1454        nullable: false,
1455        heap_type: HeapType::Concrete(intr.raw_array),
1456    }))?;
1457    let order = emitter.add_anonymous_local(ValType::Ref(RefType {
1458        nullable: false,
1459        heap_type: HeapType::Concrete(intr.raw_index_array),
1460    }))?;
1461    emitter.instruction(Instruction::LocalGet(value_local));
1462    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(intr.set)));
1463    emitter.instruction(Instruction::LocalTee(backing));
1464    emitter.instruction(Instruction::StructGet {
1465        struct_type_index: intr.set,
1466        field_index: 1,
1467    });
1468    emitter.instruction(Instruction::LocalSet(elements));
1469    emitter.instruction(Instruction::LocalGet(backing));
1470    emitter.instruction(Instruction::StructGet {
1471        struct_type_index: intr.set,
1472        field_index: 3,
1473    });
1474    emitter.instruction(Instruction::LocalSet(order));
1475    emit_ordered_collection_members_test(
1476        emitter,
1477        ctx,
1478        CollectionStorage {
1479            backing,
1480            backing_type: intr.set,
1481            order_len_field: 4,
1482            order,
1483        },
1484        &[(elements, element)],
1485        validator_state,
1486    )?;
1487    emitter.emit_else();
1488    emitter.instruction(Instruction::I32Const(0));
1489    emitter.emit_end();
1490    Ok(())
1491}
1492
1493fn emit_map_carrier_test(
1494    emitter: &mut FunctionEmitter,
1495    ctx: &CodegenCtx,
1496    value_local: u32,
1497    key: &Type,
1498    value: &Type,
1499    validator_state: Option<ValidatorState>,
1500) -> Result<(), crate::compiler_error::CompilerFailure> {
1501    let intr = ctx
1502        .symbols
1503        .intrinsic_type_indices()
1504        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1505    emit_ref_test(emitter, value_local, intr.map);
1506    emitter.emit_if(BlockType::Result(ValType::I32));
1507    let backing = emitter.add_anonymous_local(ValType::Ref(RefType {
1508        nullable: false,
1509        heap_type: HeapType::Concrete(intr.map),
1510    }))?;
1511    let keys = emitter.add_anonymous_local(ValType::Ref(RefType {
1512        nullable: false,
1513        heap_type: HeapType::Concrete(intr.raw_array),
1514    }))?;
1515    let values = emitter.add_anonymous_local(ValType::Ref(RefType {
1516        nullable: false,
1517        heap_type: HeapType::Concrete(intr.raw_array),
1518    }))?;
1519    let order = emitter.add_anonymous_local(ValType::Ref(RefType {
1520        nullable: false,
1521        heap_type: HeapType::Concrete(intr.raw_index_array),
1522    }))?;
1523    emitter.instruction(Instruction::LocalGet(value_local));
1524    emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(intr.map)));
1525    emitter.instruction(Instruction::LocalTee(backing));
1526    emitter.instruction(Instruction::StructGet {
1527        struct_type_index: intr.map,
1528        field_index: 1,
1529    });
1530    emitter.instruction(Instruction::LocalSet(keys));
1531    emitter.instruction(Instruction::LocalGet(backing));
1532    emitter.instruction(Instruction::StructGet {
1533        struct_type_index: intr.map,
1534        field_index: 2,
1535    });
1536    emitter.instruction(Instruction::LocalSet(values));
1537    emitter.instruction(Instruction::LocalGet(backing));
1538    emitter.instruction(Instruction::StructGet {
1539        struct_type_index: intr.map,
1540        field_index: 4,
1541    });
1542    emitter.instruction(Instruction::LocalSet(order));
1543    emit_ordered_collection_members_test(
1544        emitter,
1545        ctx,
1546        CollectionStorage {
1547            backing,
1548            backing_type: intr.map,
1549            order_len_field: 5,
1550            order,
1551        },
1552        &[(keys, key), (values, value)],
1553        validator_state,
1554    )?;
1555    emitter.emit_else();
1556    emitter.instruction(Instruction::I32Const(0));
1557    emitter.emit_end();
1558    Ok(())
1559}
1560
1561fn emit_ordered_collection_members_test(
1562    emitter: &mut FunctionEmitter,
1563    ctx: &CodegenCtx,
1564    storage: CollectionStorage,
1565    payloads: &[(u32, &Type)],
1566    validator_state: Option<ValidatorState>,
1567) -> Result<(), crate::compiler_error::CompilerFailure> {
1568    let intr = ctx
1569        .symbols
1570        .intrinsic_type_indices()
1571        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1572    let acc = emitter.add_anonymous_local(ValType::I32)?;
1573    let cursor = emitter.add_anonymous_local(ValType::I32)?;
1574    let slot = emitter.add_anonymous_local(ValType::I32)?;
1575    let member = emitter.add_anonymous_local(scratch_object_ty(intr.object))?;
1576    emitter.instruction(Instruction::I32Const(1));
1577    emitter.instruction(Instruction::LocalSet(acc));
1578    emitter.instruction(Instruction::I32Const(0));
1579    emitter.instruction(Instruction::LocalSet(cursor));
1580    emitter.emit_block(BlockType::Empty);
1581    emitter.emit_loop(BlockType::Empty);
1582    emitter.instruction(Instruction::LocalGet(cursor));
1583    emitter.instruction(Instruction::LocalGet(storage.backing));
1584    emitter.instruction(Instruction::StructGet {
1585        struct_type_index: storage.backing_type,
1586        field_index: storage.order_len_field,
1587    });
1588    emitter.instruction(Instruction::I32GeU);
1589    emitter.instruction(Instruction::BrIf(1));
1590    emitter.instruction(Instruction::LocalGet(storage.order));
1591    emitter.instruction(Instruction::LocalGet(cursor));
1592    emitter.instruction(Instruction::ArrayGet(intr.raw_index_array));
1593    emitter.instruction(Instruction::LocalTee(slot));
1594    emitter.instruction(Instruction::I32Const(0));
1595    emitter.instruction(Instruction::I32GeS);
1596    emitter.emit_if(BlockType::Empty);
1597    for (position, (payload, ty)) in payloads.iter().enumerate() {
1598        let parent_path = diagnostic::index(emitter, ctx, cursor);
1599        let member_path = diagnostic::field(
1600            emitter,
1601            ctx,
1602            if payloads.len() == 1 || position == 1 {
1603                "value"
1604            } else {
1605                "key"
1606            },
1607        );
1608        emitter.instruction(Instruction::LocalGet(*payload));
1609        emitter.instruction(Instruction::LocalGet(slot));
1610        emitter.instruction(Instruction::ArrayGet(intr.raw_array));
1611        emitter.instruction(Instruction::LocalSet(member));
1612        emitter.instruction(Instruction::LocalGet(acc));
1613        emit_structural_test_inner(
1614            emitter,
1615            ctx,
1616            member,
1617            ty,
1618            &mut std::collections::BTreeSet::new(),
1619            validator_state,
1620        )?;
1621        diagnostic::pop(emitter, member_path);
1622        diagnostic::pop(emitter, parent_path);
1623        emitter.instruction(Instruction::I32And);
1624        emitter.instruction(Instruction::LocalSet(acc));
1625    }
1626    emitter.emit_end();
1627    emitter.instruction(Instruction::LocalGet(cursor));
1628    emitter.instruction(Instruction::I32Const(1));
1629    emitter.instruction(Instruction::I32Add);
1630    emitter.instruction(Instruction::LocalSet(cursor));
1631    emitter.instruction(Instruction::Br(0));
1632    emitter.emit_end();
1633    emitter.emit_end();
1634    emitter.instruction(Instruction::LocalGet(acc));
1635    Ok(())
1636}
1637
1638fn emit_validator_state(
1639    emitter: &mut FunctionEmitter,
1640    ctx: &CodegenCtx,
1641    validator_state: Option<ValidatorState>,
1642) -> Result<u32, crate::compiler_error::CompilerFailure> {
1643    let intr = ctx
1644        .symbols
1645        .intrinsic_type_indices()
1646        .ok_or_else(|| super::internal_failure("intrinsic types are not declared"))?;
1647    let visited = if let Some(state) = validator_state {
1648        state.visited
1649    } else {
1650        let local = emitter.add_anonymous_local(ValType::Ref(RefType {
1651            nullable: false,
1652            heap_type: HeapType::Concrete(intr.raw_array),
1653        }))?;
1654        emitter.instruction(Instruction::I32Const(RECURSIVE_VALIDATOR_CAPACITY * 2 + 2));
1655        emitter.instruction(Instruction::ArrayNewDefault(intr.raw_array));
1656        emitter.instruction(Instruction::LocalSet(local));
1657        local
1658    };
1659    diagnostic::save_recursive(emitter, ctx, visited)?;
1660    emitter.instruction(Instruction::LocalGet(visited));
1661    if let Some(state) = validator_state {
1662        emitter.instruction(Instruction::LocalGet(state.validator_ids));
1663        emitter.instruction(Instruction::LocalGet(state.depth));
1664        emitter.instruction(Instruction::I32Const(1));
1665        emitter.instruction(Instruction::I32Add);
1666    } else {
1667        emitter.instruction(Instruction::I32Const(RECURSIVE_VALIDATOR_CAPACITY));
1668        emitter.instruction(Instruction::ArrayNewDefault(intr.raw_index_array));
1669        emitter.instruction(Instruction::I32Const(0));
1670    }
1671    Ok(visited)
1672}
1673
1674/// Body of a recursive runtime validator:
1675/// `(ref null $Object, ref $rawArray visited, ref $rawIndexArray ids, i32 depth, ref $ObjectFields types) -> i32`.
1676/// A `(value, validator)` pair already present on the active recursion path
1677/// closes a valid cycle without conflating mutually recursive shapes.
1678/// The fixed-capacity path rejects an implausibly deep acyclic graph rather than
1679/// overflowing the Wasm stack or accepting an unchecked tail.
1680pub(crate) fn emit_runtime_validator_body(
1681    ctx: &CodegenCtx,
1682    key: &Type,
1683    body: &Type,
1684    rejects_polymorphic_edge: bool,
1685    validator_id: i32,
1686) -> Result<Function, crate::compiler_error::CompilerFailure> {
1687    let intr = ctx
1688        .symbols
1689        .intrinsic_type_indices()
1690        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1691    let object_idx = intr.object;
1692    let object_ref_null = ValType::Ref(RefType {
1693        nullable: true,
1694        heap_type: HeapType::Concrete(object_idx),
1695    });
1696    let raw_array_ref = ValType::Ref(RefType {
1697        nullable: false,
1698        heap_type: HeapType::Concrete(intr.raw_array),
1699    });
1700    let raw_index_array_ref = ValType::Ref(RefType {
1701        nullable: false,
1702        heap_type: HeapType::Concrete(intr.raw_index_array),
1703    });
1704    let ident = |name: &str| Ident {
1705        name: name.to_string(),
1706        span: Span::at(ctx.file),
1707    };
1708    let mut emitter = FunctionEmitter::new(
1709        ctx,
1710        &[
1711            (ident("v"), object_ref_null),
1712            (ident("visited"), raw_array_ref),
1713            (ident("validator_ids"), raw_index_array_ref),
1714            (ident("depth"), ValType::I32),
1715            (
1716                ident("types"),
1717                super::runtime_descriptors::environment_type(ctx.symbols)?,
1718            ),
1719        ],
1720    )?;
1721    super::runtime_descriptors::bind(
1722        &mut emitter,
1723        &super::runtime_descriptors::parameters(key),
1724        4,
1725    )?;
1726    if rejects_polymorphic_edge {
1727        emitter.instruction(Instruction::I32Const(0));
1728        return emitter.build();
1729    }
1730    diagnostic::enter_recursive(&mut emitter, ctx);
1731    let checkpoint = diagnostic::checkpoint(&mut emitter, ctx);
1732    let parameters = super::runtime_descriptors::parameters(key);
1733    let seen = emitter.add_anonymous_local(ValType::I32)?;
1734    let index = emitter.add_anonymous_local(ValType::I32)?;
1735    emitter.instruction(Instruction::I32Const(0));
1736    emitter.instruction(Instruction::LocalSet(seen));
1737    emitter.instruction(Instruction::I32Const(0));
1738    emitter.instruction(Instruction::LocalSet(index));
1739    emitter.emit_block(BlockType::Empty);
1740    emitter.emit_loop(BlockType::Empty);
1741    emitter.instruction(Instruction::LocalGet(index));
1742    emitter.instruction(Instruction::LocalGet(3));
1743    emitter.instruction(Instruction::I32GeU);
1744    emitter.instruction(Instruction::BrIf(1));
1745    emitter.instruction(Instruction::LocalGet(1));
1746    emitter.instruction(Instruction::LocalGet(index));
1747    emitter.instruction(Instruction::I32Const(2));
1748    emitter.instruction(Instruction::I32Mul);
1749    emitter.instruction(Instruction::ArrayGet(intr.raw_array));
1750    emitter.instruction(Instruction::LocalGet(0));
1751    emitter.instruction(Instruction::RefEq);
1752    emitter.instruction(Instruction::LocalGet(2));
1753    emitter.instruction(Instruction::LocalGet(index));
1754    emitter.instruction(Instruction::ArrayGet(intr.raw_index_array));
1755    emitter.instruction(Instruction::I32Const(validator_id));
1756    emitter.instruction(Instruction::I32Eq);
1757    emitter.instruction(Instruction::I32And);
1758    emitter.emit_if(BlockType::Empty);
1759    emit_same_validator_environment(&mut emitter, ctx, index, parameters.len())?;
1760    emitter.instruction(Instruction::LocalSet(seen));
1761    emitter.instruction(Instruction::LocalGet(seen));
1762    emitter.emit_if(BlockType::Empty);
1763    emitter.instruction(Instruction::Br(3));
1764    emitter.emit_end();
1765    emitter.emit_end();
1766    emitter.instruction(Instruction::LocalGet(index));
1767    emitter.instruction(Instruction::I32Const(1));
1768    emitter.instruction(Instruction::I32Add);
1769    emitter.instruction(Instruction::LocalSet(index));
1770    emitter.instruction(Instruction::Br(0));
1771    emitter.emit_end();
1772    emitter.emit_end();
1773    emitter.instruction(Instruction::LocalGet(seen));
1774    emitter.emit_if(BlockType::Result(ValType::I32));
1775    emitter.instruction(Instruction::I32Const(1));
1776    emitter.emit_else();
1777    emitter.instruction(Instruction::LocalGet(3));
1778    emitter.instruction(Instruction::I32Const(RECURSIVE_VALIDATOR_CAPACITY));
1779    emitter.instruction(Instruction::I32GeU);
1780    emitter.emit_if(BlockType::Result(ValType::I32));
1781    emitter.instruction(Instruction::I32Const(0));
1782    emitter.emit_else();
1783    emitter.instruction(Instruction::LocalGet(1));
1784    emitter.instruction(Instruction::LocalGet(3));
1785    emitter.instruction(Instruction::I32Const(2));
1786    emitter.instruction(Instruction::I32Mul);
1787    emitter.instruction(Instruction::LocalGet(0));
1788    emitter.instruction(Instruction::ArraySet(intr.raw_array));
1789    if !parameters.is_empty() {
1790        emit_store_validator_environment(&mut emitter, ctx)?;
1791    }
1792    emitter.instruction(Instruction::LocalGet(2));
1793    emitter.instruction(Instruction::LocalGet(3));
1794    emitter.instruction(Instruction::I32Const(validator_id));
1795    emitter.instruction(Instruction::ArraySet(intr.raw_index_array));
1796    let mut interface_stack = std::collections::BTreeSet::new();
1797    if key.peel() == body.peel()
1798        && let Some(crate::FieldNarrowingTest::Interface(test)) =
1799            ctx.ta.runtime_type_tests.get(key.peel())
1800    {
1801        emit_interface_test_inner(
1802            &mut emitter,
1803            ctx,
1804            0,
1805            test,
1806            &mut interface_stack,
1807            Some(ValidatorState {
1808                visited: 1,
1809                validator_ids: 2,
1810                depth: 3,
1811            }),
1812        )?;
1813    } else {
1814        emit_structural_test_inner(
1815            &mut emitter,
1816            ctx,
1817            0,
1818            body,
1819            &mut interface_stack,
1820            Some(ValidatorState {
1821                visited: 1,
1822                validator_ids: 2,
1823                depth: 3,
1824            }),
1825        )?;
1826    }
1827    emitter.emit_end();
1828    emitter.emit_end();
1829    diagnostic::finish(&mut emitter, ctx, checkpoint)?;
1830    diagnostic::save_recursive(&mut emitter, ctx, 1)?;
1831    ctx.check_function_limits(&mut emitter)?;
1832    emitter.build()
1833}
1834
1835fn emit_is_non_null(emitter: &mut FunctionEmitter, value_local: u32) {
1836    emitter.instruction(Instruction::LocalGet(value_local));
1837    emitter.instruction(Instruction::RefIsNull);
1838    emitter.instruction(Instruction::I32Eqz);
1839}
1840
1841fn emit_ref_test(emitter: &mut FunctionEmitter, value_local: u32, type_idx: u32) {
1842    emitter.instruction(Instruction::LocalGet(value_local));
1843    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(type_idx)));
1844}
1845
1846fn boxed_number_idx(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1847    ctx.symbols
1848        .boxed_number_type_idx()
1849        .ok_or_else(|| crate::codegen::internal_failure("$BoxedNumber intrinsic registered"))
1850}
1851
1852fn boxed_boolean_idx(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1853    ctx.symbols
1854        .boxed_boolean_type_idx()
1855        .ok_or_else(|| crate::codegen::internal_failure("$BoxedBoolean intrinsic registered"))
1856}
1857
1858fn string_idx(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1859    ctx.symbols
1860        .string_type_idx()
1861        .ok_or_else(|| crate::codegen::internal_failure("$string intrinsic registered"))
1862}
1863
1864fn object_idx_of(ctx: &CodegenCtx) -> Result<u32, crate::compiler_error::CompilerFailure> {
1865    Ok(ctx
1866        .symbols
1867        .intrinsic_type_indices()
1868        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?
1869        .object)
1870}
1871
1872fn scratch_object_ty(object_idx: u32) -> ValType {
1873    ValType::Ref(RefType {
1874        nullable: true,
1875        heap_type: HeapType::Concrete(object_idx),
1876    })
1877}
1878
1879fn emit_cast_throw(
1880    emitter: &mut FunctionEmitter,
1881    ctx: &CodegenCtx,
1882    scratch: u32,
1883    target_ty: &Type,
1884) {
1885    ctx.latch(emit_cast_throw_checked(emitter, ctx, scratch, target_ty));
1886}
1887
1888fn emit_cast_throw_checked(
1889    emitter: &mut FunctionEmitter,
1890    ctx: &CodegenCtx,
1891    scratch: u32,
1892    target_ty: &Type,
1893) -> Result<(), crate::compiler_error::CompilerFailure> {
1894    let prefix = error_prefix(target_ty);
1895    emit_inline_string(emitter, ctx, &prefix);
1896
1897    emit_runtime_type_string(emitter, ctx, scratch);
1898
1899    let concat_idx = ctx
1900        .symbols
1901        .prelude_func_idx("string_concat")
1902        .ok_or_else(|| crate::codegen::internal_failure("string_concat imported from prelude"))?;
1903    emitter.instruction(Instruction::Call(concat_idx));
1904
1905    diagnostic::append_failure(emitter, ctx);
1906
1907    emit_type_error_from_message(emitter, ctx);
1908
1909    Ok(())
1910}
1911
1912fn emit_type_error_from_message(emitter: &mut FunctionEmitter, ctx: &CodegenCtx) {
1913    ctx.latch(emit_type_error_from_message_checked(emitter, ctx));
1914}
1915
1916fn emit_type_error_from_message_checked(
1917    emitter: &mut FunctionEmitter,
1918    ctx: &CodegenCtx,
1919) -> Result<(), crate::compiler_error::CompilerFailure> {
1920    let new_idx = ctx
1921        .symbols
1922        .prelude_func_idx("TypeError#constructor")
1923        .ok_or_else(|| {
1924            crate::codegen::internal_failure("TypeError#constructor imported from prelude")
1925        })?;
1926    emitter.instruction(Instruction::Call(new_idx));
1927
1928    // Constructor returns (ref null $Object); the throw helper narrows to
1929    // $Error so the throw carries the payload type the catch expects.
1930    crate::codegen::throw::emit_error_throw(emitter, ctx);
1931
1932    Ok(())
1933}
1934
1935pub(super) fn emit_inline_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, text: &str) {
1936    ctx.latch(emit_inline_string_checked(emitter, ctx, text));
1937}
1938
1939fn emit_inline_string_checked(
1940    emitter: &mut FunctionEmitter,
1941    ctx: &CodegenCtx,
1942    text: &str,
1943) -> Result<(), crate::compiler_error::CompilerFailure> {
1944    let intr = ctx
1945        .symbols
1946        .intrinsic_type_indices()
1947        .ok_or_else(|| crate::codegen::internal_failure("intrinsics declared"))?;
1948    let string_vtable_idx = ctx
1949        .symbols
1950        .prelude_global_idx("string_vtable")
1951        .ok_or_else(|| crate::codegen::internal_failure("string_vtable imported"))?;
1952    emitter.instruction(Instruction::GlobalGet(string_vtable_idx));
1953    ctx.latch(emit_inline_const_raw_string(emitter, ctx, text));
1954    emitter.instruction(Instruction::I64Const(0));
1955    emitter.instruction(Instruction::StructNew(intr.string));
1956
1957    Ok(())
1958}
1959
1960/// Cascades ref.test on scratch to produce a JS-typeof-style tag string;
1961/// closures first, then string/number/boolean, then null, then "object" fallthrough.
1962fn emit_runtime_type_string(emitter: &mut FunctionEmitter, ctx: &CodegenCtx, scratch: u32) {
1963    ctx.latch(emit_runtime_type_string_checked(emitter, ctx, scratch));
1964}
1965
1966fn emit_runtime_type_string_checked(
1967    emitter: &mut FunctionEmitter,
1968    ctx: &CodegenCtx,
1969    scratch: u32,
1970) -> Result<(), crate::compiler_error::CompilerFailure> {
1971    let string_type_idx = ctx
1972        .symbols
1973        .string_type_idx()
1974        .ok_or_else(|| crate::codegen::internal_failure("$string intrinsic registered"))?;
1975    let string_ref = ValType::Ref(RefType {
1976        nullable: false,
1977        heap_type: HeapType::Concrete(string_type_idx),
1978    });
1979    let block = BlockType::Result(string_ref);
1980
1981    emitter.instruction(Instruction::LocalGet(scratch));
1982    crate::codegen::function_emitter::expr::emit_is_undefined(emitter, ctx)?;
1983    emitter.emit_if(block);
1984    emit_type_tag(emitter, ctx, "undefined")?;
1985    emitter.emit_else();
1986
1987    // null gets its own arm rather than JS's "object" tag — clearer cast error messages.
1988    emitter.instruction(Instruction::LocalGet(scratch));
1989    emitter.instruction(Instruction::RefIsNull);
1990    emitter.emit_if(block);
1991    emit_type_tag(emitter, ctx, "null")?;
1992    emitter.emit_else();
1993
1994    emitter.instruction(Instruction::LocalGet(scratch));
1995    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(
1996        string_type_idx,
1997    )));
1998    emitter.emit_if(block);
1999    emit_type_tag(emitter, ctx, "string")?;
2000    emitter.emit_else();
2001
2002    let num_idx = ctx
2003        .symbols
2004        .boxed_number_type_idx()
2005        .ok_or_else(|| crate::codegen::internal_failure("$BoxedNumber intrinsic registered"))?;
2006    emitter.instruction(Instruction::LocalGet(scratch));
2007    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(num_idx)));
2008    emitter.emit_if(block);
2009    emit_type_tag(emitter, ctx, "number")?;
2010    emitter.emit_else();
2011
2012    let bool_idx = ctx
2013        .symbols
2014        .boxed_boolean_type_idx()
2015        .ok_or_else(|| crate::codegen::internal_failure("$BoxedBoolean intrinsic registered"))?;
2016    emitter.instruction(Instruction::LocalGet(scratch));
2017    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(bool_idx)));
2018    emitter.emit_if(block);
2019    emit_type_tag(emitter, ctx, "boolean")?;
2020    emitter.emit_else();
2021
2022    let closure_idx = ctx
2023        .symbols
2024        .closure_type_idx()
2025        .ok_or_else(|| crate::codegen::internal_failure("$Closure intrinsic registered"))?;
2026    emitter.instruction(Instruction::LocalGet(scratch));
2027    emitter.instruction(Instruction::RefTestNonNull(HeapType::Concrete(closure_idx)));
2028    emitter.emit_if(block);
2029    emit_type_tag(emitter, ctx, "function")?;
2030    emitter.emit_else();
2031
2032    emit_type_tag(emitter, ctx, "object")?;
2033
2034    for _ in 0..5 {
2035        emitter.emit_end();
2036    }
2037
2038    emitter.emit_end();
2039    Ok(())
2040}
2041
2042/// Whether `ty` spells `undefined`. A required `unknown` field still needs its key.
2043fn admits_undefined(ty: &Type) -> bool {
2044    match ty.peel() {
2045        Type::Undefined | Type::Void => true,
2046        Type::Union(members) => members.iter().any(admits_undefined),
2047        _ => false,
2048    }
2049}
2050
2051/// A tag the string pool interned is read from its data segment, which is
2052/// smaller than spelling it out at each of the many failure paths that name it.
2053fn emit_type_tag(
2054    emitter: &mut FunctionEmitter,
2055    ctx: &CodegenCtx,
2056    tag: &str,
2057) -> Result<(), crate::compiler_error::CompilerFailure> {
2058    match ctx.strings.lookup_text(tag) {
2059        Some(pool_idx) => {
2060            crate::codegen::function_emitter::emit_pooled_string(emitter, ctx, pool_idx)
2061        }
2062        None => emit_inline_string_checked(emitter, ctx, tag),
2063    }
2064}
2065
2066/// Visited entries pair the value with its effective type arguments. Forwarded
2067/// predicates preserve identity; growing arguments construct fresh predicates.
2068fn emit_same_validator_environment(
2069    emitter: &mut FunctionEmitter,
2070    ctx: &CodegenCtx,
2071    index: u32,
2072    count: usize,
2073) -> Result<(), crate::compiler_error::CompilerFailure> {
2074    emitter.instruction(Instruction::I32Const(1));
2075    if count == 0 {
2076        return Ok(());
2077    }
2078    let intr = ctx
2079        .symbols
2080        .intrinsic_type_indices()
2081        .ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?;
2082    let closure = ctx
2083        .symbols
2084        .closure_struct_type_idx(super::field_guards::signature())
2085        .ok_or_else(|| crate::codegen::internal_failure("descriptor closure"))?;
2086    for slot in 0..count {
2087        emitter.instruction(Instruction::LocalGet(1));
2088        emitter.instruction(Instruction::LocalGet(index));
2089        emitter.instruction(Instruction::I32Const(2));
2090        emitter.instruction(Instruction::I32Mul);
2091        emitter.instruction(Instruction::I32Const(1));
2092        emitter.instruction(Instruction::I32Add);
2093        emitter.instruction(Instruction::ArrayGet(intr.raw_array));
2094        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(closure)));
2095        emitter.instruction(Instruction::StructGet {
2096            struct_type_index: closure,
2097            field_index: 2,
2098        });
2099        emitter.instruction(Instruction::RefCastNonNull(HeapType::Concrete(
2100            intr.object_fields,
2101        )));
2102        emitter.instruction(Instruction::I32Const(slot as i32));
2103        emitter.instruction(Instruction::ArrayGet(intr.object_fields));
2104        emitter.instruction(Instruction::LocalGet(4));
2105        emitter.instruction(Instruction::I32Const(slot as i32));
2106        emitter.instruction(Instruction::ArrayGet(intr.object_fields));
2107        emitter.instruction(Instruction::RefEq);
2108        emitter.instruction(Instruction::I32And);
2109    }
2110    Ok(())
2111}
2112
2113fn emit_store_validator_environment(
2114    emitter: &mut FunctionEmitter,
2115    ctx: &CodegenCtx,
2116) -> Result<(), crate::compiler_error::CompilerFailure> {
2117    let intr = ctx
2118        .symbols
2119        .intrinsic_type_indices()
2120        .ok_or_else(|| crate::codegen::internal_failure("intrinsics"))?;
2121    let closure = ctx
2122        .symbols
2123        .closure_struct_type_idx(super::field_guards::signature())
2124        .ok_or_else(|| crate::codegen::internal_failure("descriptor closure"))?;
2125    emitter.instruction(Instruction::LocalGet(1));
2126    emitter.instruction(Instruction::LocalGet(3));
2127    emitter.instruction(Instruction::I32Const(2));
2128    emitter.instruction(Instruction::I32Mul);
2129    emitter.instruction(Instruction::I32Const(1));
2130    emitter.instruction(Instruction::I32Add);
2131    emitter.instruction(Instruction::GlobalGet(
2132        ctx.symbols
2133            .closure_vtable_global_idx()
2134            .ok_or_else(|| crate::codegen::internal_failure("closure vtable"))?,
2135    ));
2136    emitter.instruction(Instruction::RefFunc(
2137        *ctx.symbols
2138            .type_descriptor_functions
2139            .get(&Type::Unknown)
2140            .ok_or_else(|| {
2141                crate::codegen::internal_failure(
2142                    "validator environment descriptor is not registered",
2143                )
2144            })?,
2145    ));
2146    emitter.instruction(Instruction::LocalGet(4));
2147    emitter.instruction(Instruction::I64Const(0));
2148    emitter.instruction(Instruction::StructNew(closure));
2149    emitter.instruction(Instruction::ArraySet(intr.raw_array));
2150
2151    Ok(())
2152}
2153
2154fn emit_index_conformance(
2155    emitter: &mut FunctionEmitter,
2156    ctx: &CodegenCtx,
2157    object: u32,
2158    index: &crate::IndexSignature,
2159    interface_stack: &mut std::collections::BTreeSet<Type>,
2160    validator_state: Option<ValidatorState>,
2161) -> Result<(), crate::compiler_error::CompilerFailure> {
2162    emitter.instruction(Instruction::LocalGet(object));
2163    let Some(symbol) = ctx.require(
2164        ctx.symbols
2165            .prelude_func_idx("ObjectConstructor##recordValues"),
2166        "record validator imported",
2167    ) else {
2168        return Ok(());
2169    };
2170    emitter.instruction(Instruction::Call(symbol));
2171    let values = emitter.add_anonymous_local(scratch_object_ty(object_idx_of(ctx)?))?;
2172    emitter.instruction(Instruction::LocalSet(values));
2173    emit_structural_test_inner(
2174        emitter,
2175        ctx,
2176        values,
2177        &Type::Array(index.value.clone()),
2178        interface_stack,
2179        validator_state,
2180    )?;
2181    Ok(())
2182}
2183
2184#[cfg(test)]
2185mod representation_invariant_tests {
2186    use super::*;
2187    use crate::codegen::invariant_tests::{assert_internal, with_context};
2188
2189    #[test]
2190    fn primitive_representation_test_returns_internal_failure() {
2191        with_context(
2192            &crate::TypedAst::new(),
2193            &crate::codegen::SymbolTable::default(),
2194            |ctx| {
2195                let mut emitter = FunctionEmitter::new(ctx, &[]).unwrap();
2196                assert_internal(
2197                    emit_representation_test(&mut emitter, ctx, 0, &Type::Number).unwrap_err(),
2198                );
2199            },
2200        );
2201    }
2202}