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