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