Skip to main content

submilli_engine/codegen/
cast_check.rs

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