Skip to main content

submilli_engine/codegen/
analysis.rs

1use std::collections::BTreeSet;
2
3use crate::codegen::bigint_pool::BigIntPool;
4use crate::codegen::closures::{ClosureMeta, ClosureSig};
5use crate::codegen::dependency_usage::DependencyUsage;
6use crate::codegen::function_adapters::AdapterMeta;
7use crate::codegen::string_pool::StringPool;
8use crate::codegen::{bounds, cast_check, throw};
9use crate::{
10    AccessorKind, BinOp, ClosureBody, ExprId, Intrinsic, PostfixTarget, Shape, StmtId, Type,
11    TypedAst, TypedChainPart, TypedExprKind, TypedObjectFieldSource, TypedStmtKind,
12    TypedSwitchValue,
13};
14
15pub struct CodegenAnalysis {
16    pub string_pool: StringPool,
17    pub bigint_pool: BigIntPool,
18    pub dependency_usage: DependencyUsage,
19    pub closure_metas: Vec<ClosureMeta>,
20    pub adapter_metas: Vec<AdapterMeta>,
21    /// Closure shapes named by any type the module mentions; see
22    /// [`crate::codegen::closures`] for how this fits the other sources.
23    pub mentioned_closure_sigs: Vec<ClosureSig>,
24    pub extra_field_names: Vec<String>,
25    adapter_seen: BTreeSet<crate::MangledName>,
26}
27
28impl CodegenAnalysis {
29    pub fn collect(
30        ta: &TypedAst,
31        dependencies: &[&crate::PackageDeclaration],
32    ) -> Result<Self, crate::compiler_error::CompilerFailure> {
33        let mut analysis = Self {
34            string_pool: StringPool::default(),
35            bigint_pool: BigIntPool::default(),
36            dependency_usage: DependencyUsage::empty(),
37            closure_metas: Vec::new(),
38            adapter_metas: Vec::new(),
39            mentioned_closure_sigs: Vec::new(),
40            extra_field_names: Vec::new(),
41            adapter_seen: BTreeSet::new(),
42        };
43
44        // Generated serializers also cover dependency shapes discovered later,
45        // and a static-dispatch binding used as a value reads as a host object.
46        for helper in ["#toJson", "#staticValue"] {
47            analysis.dependency_usage.note_member(crate::mangle::extend(
48                &crate::mangle::prelude("ObjectConstructor"),
49                helper,
50            ));
51        }
52
53        for (id, ty) in &ta.runtime_source_types {
54            let span = ta
55                .try_expr(*id)
56                .map_err(crate::codegen::arena_failure)?
57                .span;
58            analysis
59                .visit_type(ty)
60                .map_err(|error| error.with_span(span))?;
61            analysis
62                .string_pool
63                .intern_text(&cast_check::error_prefix(ty));
64        }
65        if !ta.runtime_source_types.is_empty() {
66            for text in cast_check::TYPE_TAG_STRINGS {
67                analysis.string_pool.intern_text(text);
68            }
69            for name in [
70                "numeric",
71                "inc",
72                "dec",
73                "to_number",
74                "to_index",
75                "to_string",
76                "member",
77                "invoke",
78                "property",
79            ] {
80                analysis
81                    .dependency_usage
82                    .note_value(crate::mangle::prelude(&format!("__value_{name}")));
83            }
84        }
85        for (id, types) in &ta.runtime_chain_types {
86            let span = ta
87                .try_expr(*id)
88                .map_err(crate::codegen::arena_failure)?
89                .span;
90            for ty in types {
91                let ty = crate::typechecker::infer::narrowing::strip_nullish(ty);
92                analysis.visit_type_at(&ty, span)?;
93                analysis
94                    .string_pool
95                    .intern_text(&cast_check::error_prefix(&ty));
96            }
97        }
98
99        for shape in &ta.shapes {
100            if matches!(shape, Shape::Object { .. }) {
101                analysis
102                    .dependency_usage
103                    .collect_typed_object_stringify_host_value();
104            }
105            analysis.dependency_usage.note_shape(shape.clone());
106        }
107        for g in &ta.globals {
108            analysis.visit_type_at(&g.ty, g.span)?;
109        }
110        let guarded_bindings = super::init_guard::guarded_globals(ta)?
111            .into_iter()
112            .map(|guard| guard.binding)
113            .chain(
114                super::init_guard::guarded_classes(ta)?
115                    .into_iter()
116                    .map(|class| class.name),
117            )
118            .collect::<Vec<_>>();
119        if !guarded_bindings.is_empty() {
120            analysis
121                .dependency_usage
122                .note_type(crate::mangle::prelude("ReferenceError"));
123        }
124        for binding in guarded_bindings {
125            analysis
126                .string_pool
127                .intern_text(&super::init_guard::before_initialization_message(&binding));
128        }
129
130        if throw::body_end_may_be_reached(ta) {
131            analysis
132                .string_pool
133                .intern_text(throw::MISSING_RETURN_VALUE_MESSAGE);
134        }
135        for f in &ta.functions {
136            for p in &f.params {
137                if p.boxed {
138                    analysis.note_binding_cell();
139                }
140                analysis
141                    .visit_type(&p.ty)
142                    .map_err(|error| error.with_span(p.name.span))?;
143            }
144            analysis.visit_type_at(&f.return_type, f.span)?;
145            analysis.walk_stmt(ta, f.body)?;
146        }
147        for &stmt_id in &ta.top_level_statements {
148            analysis.walk_stmt(ta, stmt_id)?;
149        }
150        for stmt_id in ta.class_body_roots() {
151            analysis.walk_stmt(ta, stmt_id)?;
152        }
153        for expr_id in ta.class_field_initializers() {
154            analysis.walk_expr(ta, expr_id)?;
155        }
156        if !ta.runtime_class_fields.is_empty()
157            || ta
158                .runtime_class_parameters
159                .values()
160                .any(|params| !params.is_empty())
161            || ta
162                .functions
163                .iter()
164                .any(|function| !function.generics.is_empty())
165            || ta
166                .runtime_class_contexts
167                .values()
168                .any(|contexts| !contexts.is_empty())
169        {
170            analysis
171                .mentioned_closure_sigs
172                .push(super::field_guards::signature());
173        }
174        if let Some(argument) = &ta.finalizer_argument {
175            analysis.note_cast(&argument.target, Some(&argument.check))?;
176        }
177        analysis.note_narrowing_checks(ta)?;
178        analysis.note_inherited_conversions(ta, dependencies)?;
179        for test in ta.runtime_type_tests.values() {
180            analysis.note_narrowing_test(test)?;
181        }
182        analysis.note_dependency_narrowing_checks(dependencies)?;
183
184        // Imported class members can add closure adapters after dependency
185        // selection, even when the source mentions no function-valued types.
186        for name in [
187            "__value_defaults_fit",
188            "__value_invoke_defaults",
189            "Number#toString",
190            "string_concat",
191        ] {
192            analysis
193                .dependency_usage
194                .note_value(crate::mangle::prelude(name));
195        }
196
197        Ok(analysis)
198    }
199
200    /// A narrowed field's read guard throws a constant message and, when its test
201    /// is structural, tests a shape; both need the same interning and imports a
202    /// `Cast` gets, and neither is reachable from any expression, so no walk above
203    /// finds them.
204    fn note_narrowing_checks(
205        &mut self,
206        ta: &TypedAst,
207    ) -> Result<(), crate::compiler_error::CompilerFailure> {
208        for decl in &ta.types {
209            let crate::TypedTypeDecl::Class(class) = decl else {
210                continue;
211            };
212            if class
213                .methods
214                .iter()
215                .any(|method| matches!(method.name.name.as_str(), "toString" | "toJson"))
216            {
217                self.note_conversion_dispatch()?;
218            }
219            for check in class
220                .fields
221                .iter()
222                .filter_map(|f| f.narrowing_check.as_ref())
223            {
224                self.note_narrowing_check(check)?;
225            }
226        }
227        Ok(())
228    }
229
230    /// A local subclass emits conversion forwarders even when the authored
231    /// methods live in a dependency and no expression calls them explicitly.
232    fn note_inherited_conversions(
233        &mut self,
234        ta: &TypedAst,
235        dependencies: &[&crate::PackageDeclaration],
236    ) -> Result<(), crate::compiler_error::CompilerFailure> {
237        let mut parents: Vec<_> = ta
238            .types
239            .iter()
240            .filter_map(|decl| match decl {
241                crate::TypedTypeDecl::Class(class) => class.extends.as_ref(),
242                _ => None,
243            })
244            .collect();
245        let mut visited = BTreeSet::new();
246        while let Some(parent) = parents.pop() {
247            if !visited.insert(parent) {
248                continue;
249            }
250            let Some(symbol) = dependencies
251                .iter()
252                .flat_map(|package| package.runtime_types.values().chain(package.types.values()))
253                .find(|symbol| &symbol.mangled_name == parent)
254            else {
255                continue;
256            };
257            let crate::TypeKind::Class {
258                methods, extends, ..
259            } = &symbol.kind
260            else {
261                continue;
262            };
263            if methods.contains_key("toString") || methods.contains_key("toJson") {
264                self.note_conversion_dispatch()?;
265                return Ok(());
266            }
267            if let Some(extends) = extends {
268                parents.push(&extends.parent);
269            }
270        }
271        Ok(())
272    }
273
274    fn note_conversion_dispatch(&mut self) -> Result<(), crate::compiler_error::CompilerFailure> {
275        self.string_pool.intern_text("Value is not callable");
276        self.string_pool
277            .intern_text("Unbound function has no this receiver");
278        self.string_pool
279            .intern_text(&cast_check::error_prefix(&Type::String));
280        for tag in cast_check::TYPE_TAG_STRINGS {
281            self.string_pool.intern_text(tag);
282        }
283        self.mentioned_closure_sigs
284            .push(ClosureSig::of(0, &Type::String)?);
285        Ok(())
286    }
287
288    fn note_dependency_narrowing_checks(
289        &mut self,
290        dependencies: &[&crate::PackageDeclaration],
291    ) -> Result<(), crate::compiler_error::CompilerFailure> {
292        for package in dependencies {
293            for symbol in package.runtime_types.values().chain(package.types.values()) {
294                let crate::TypeKind::Class {
295                    narrowing_checks, ..
296                } = &symbol.kind
297                else {
298                    continue;
299                };
300                for check in narrowing_checks.values() {
301                    self.note_narrowing_check(check)?;
302                }
303            }
304        }
305        Ok(())
306    }
307
308    fn note_narrowing_check(
309        &mut self,
310        check: &crate::FieldNarrowingCheck,
311    ) -> Result<(), crate::compiler_error::CompilerFailure> {
312        self.string_pool.intern_text(&check.message);
313        self.note_narrowing_test(&check.test)?;
314
315        Ok(())
316    }
317
318    fn note_narrowing_test(
319        &mut self,
320        test: &crate::FieldNarrowingTest,
321    ) -> Result<(), crate::compiler_error::CompilerFailure> {
322        match test {
323            crate::FieldNarrowingTest::Shape(shape) => {
324                self.visit_type(shape)?;
325                self.note_shape_member_names(shape);
326            }
327            crate::FieldNarrowingTest::Interface(test) => {
328                let shape = Type::Object {
329                    index: test.index.clone(),
330                    fields: test.members.clone(),
331                };
332                self.visit_type(&shape)?;
333                self.note_shape_member_names(&shape);
334            }
335            crate::FieldNarrowingTest::NonNull
336            | crate::FieldNarrowingTest::Substituted
337            | crate::FieldNarrowingTest::Representation => {}
338        };
339        Ok(())
340    }
341
342    /// What `value as target_ty` needs: its error text and, for a checked
343    /// cast, the shape it tests.
344    fn note_cast(
345        &mut self,
346        target_ty: &Type,
347        check: Option<&Type>,
348    ) -> Result<(), crate::compiler_error::CompilerFailure> {
349        self.string_pool
350            .intern_text(&cast_check::error_prefix(target_ty));
351        for tag in cast_check::TYPE_TAG_STRINGS {
352            self.string_pool.intern_text(tag);
353        }
354        self.visit_type(target_ty)?;
355        if let Some(check) = check {
356            self.visit_type(check)?;
357            self.note_shape_member_names(check);
358        }
359        Ok(())
360    }
361
362    /// The per-name `$string` globals the shape's field scan reads: for each
363    /// property, its own name and its `get <prop>` accessor name, plus the getter
364    /// closure sig the accessor branch casts to. A recorded shape reaches neither
365    /// `ta.shapes` nor any expression, so a member named nowhere else in the
366    /// module has no global unless this registers it — and the field-scan lookup
367    /// panics rather than importing one on demand.
368    ///
369    /// The accessor half is interned off the *test*, not off any accessor
370    /// declaration, for the reason [`Self::note_shaped_property_access`] gives:
371    /// whether a value is accessor-backed is a whole-program fact, and the module
372    /// running the cast need not see the class that declares the accessor.
373    ///
374    /// `runtime_type_is_testable` defines the recursively structural `Shape`
375    /// cases. Interface descriptors are registered separately by
376    /// `note_narrowing_test`, including nested member names. The arms below are
377    /// therefore the remaining shape forms that can nest an object; a new
378    /// nesting arm in `emit_structural_test` needs one here too.
379    fn note_shape_member_names(&mut self, ty: &Type) {
380        match ty.peel() {
381            Type::Object { fields, index } => {
382                if let Some(index) = index {
383                    self.note_record_helpers();
384                    self.note_shape_member_names(&index.value);
385                }
386                if !fields.is_empty() {
387                    self.mentioned_closure_sigs.push(
388                        crate::codegen::classes::accessor_closure_sig(AccessorKind::Get),
389                    );
390                }
391                for (name, field) in fields {
392                    self.extra_field_names.push(name.clone());
393                    self.extra_field_names
394                        .push(crate::codegen::classes::accessor_getter_name(name));
395                    self.note_shape_member_names(&field.ty);
396                }
397            }
398            Type::Array(elem) => self.note_shape_member_names(elem),
399            Type::Tuple(elems) => {
400                for e in elems {
401                    self.note_shape_member_names(e);
402                }
403            }
404            Type::Union(members) => {
405                for m in members {
406                    self.note_shape_member_names(m);
407                }
408            }
409            _ => {}
410        }
411    }
412
413    /// Every type the module mentions funnels through here. `dependency_usage`
414    /// needs it to import the symbols the type names; closure codegen needs the
415    /// function-typed positions inside it. These are post-substitution types, so
416    /// a generic instantiated with `void` contributes the sig it lowers to
417    /// rather than the one its declaration spells.
418    ///
419    /// Call this rather than `dependency_usage.collect_type` directly —
420    /// bypassing it drops the closure half silently, and the failure surfaces as
421    /// an internal compiler failure far from the omission.
422    fn visit_type_at(
423        &mut self,
424        ty: &Type,
425        span: crate::Span,
426    ) -> Result<(), crate::compiler_error::CompilerFailure> {
427        self.visit_type(ty).map_err(|error| error.with_span(span))
428    }
429
430    fn visit_type(&mut self, ty: &Type) -> Result<(), crate::compiler_error::CompilerFailure> {
431        if let Type::Object {
432            index: Some(index), ..
433        } = ty.peel()
434        {
435            self.note_record_helpers();
436            self.visit_type(&index.value)?;
437        }
438        self.dependency_usage.collect_type(ty);
439        crate::codegen::closures::walk_type(ty, &mut self.mentioned_closure_sigs)?;
440
441        Ok(())
442    }
443
444    fn note_record_helpers(&mut self) {
445        for helper in crate::codegen::dependency_usage::RECORD_HELPERS {
446            self.dependency_usage.note_member(crate::mangle::extend(
447                &crate::mangle::prelude("ObjectConstructor"),
448                helper,
449            ));
450        }
451    }
452
453    fn walk_stmt(
454        &mut self,
455        ta: &TypedAst,
456        id: StmtId,
457    ) -> Result<(), crate::compiler_error::CompilerFailure> {
458        self.note_stmt_pre(ta, id)?;
459        match &ta.try_stmt(id).map_err(crate::codegen::arena_failure)?.kind {
460            TypedStmtKind::Let { value, .. } | TypedStmtKind::Const { value, .. } => {
461                self.walk_expr(ta, *value)?;
462            }
463            TypedStmtKind::If {
464                condition,
465                then_block,
466                else_block,
467            } => {
468                self.walk_expr(ta, *condition)?;
469                self.walk_stmt(ta, *then_block)?;
470                if let Some(else_block) = else_block {
471                    self.walk_stmt(ta, *else_block)?;
472                }
473            }
474            TypedStmtKind::While { condition, body } => {
475                self.walk_expr(ta, *condition)?;
476                self.walk_stmt(ta, *body)?;
477            }
478            TypedStmtKind::For {
479                init,
480                condition,
481                update,
482                body,
483            } => {
484                if let Some(init) = init {
485                    self.walk_stmt(ta, *init)?;
486                }
487                if let Some(condition) = condition {
488                    self.walk_expr(ta, *condition)?;
489                }
490                if let Some(update) = update {
491                    self.walk_stmt(ta, *update)?;
492                }
493                self.walk_stmt(ta, *body)?;
494            }
495            TypedStmtKind::ForOf { iter, body, .. } => {
496                self.walk_expr(ta, *iter)?;
497                self.walk_stmt(ta, *body)?;
498            }
499            TypedStmtKind::DoWhile { body, condition } => {
500                self.walk_stmt(ta, *body)?;
501                self.walk_expr(ta, *condition)?;
502            }
503            TypedStmtKind::Switch {
504                discriminant,
505                cases,
506                default,
507                ..
508            } => {
509                self.walk_expr(ta, *discriminant)?;
510                for comparison in cases
511                    .iter()
512                    .flat_map(crate::TypedSwitchCase::label_comparisons)
513                {
514                    self.walk_expr(ta, comparison)?;
515                }
516                for case in cases {
517                    self.walk_stmt(ta, case.body)?;
518                }
519                if let Some(default) = default {
520                    self.walk_stmt(ta, *default)?;
521                }
522            }
523            TypedStmtKind::Break | TypedStmtKind::Continue | TypedStmtKind::ReboxLocal { .. } => {}
524            TypedStmtKind::Return(value) => {
525                if let Some(value) = value {
526                    self.walk_expr(ta, *value)?;
527                }
528            }
529            TypedStmtKind::Expr(expr) => self.walk_expr(ta, *expr)?,
530            TypedStmtKind::Block(stmts) => {
531                for &stmt in stmts {
532                    self.walk_stmt(ta, stmt)?;
533                }
534            }
535            TypedStmtKind::AssignLocal { value, .. }
536            | TypedStmtKind::AssignGlobal { value, .. } => self.walk_expr(ta, *value)?,
537            TypedStmtKind::AssignField {
538                receiver, value, ..
539            } => {
540                self.walk_expr(ta, *receiver)?;
541                self.walk_expr(ta, *value)?;
542            }
543            TypedStmtKind::AssignIndex {
544                receiver,
545                index,
546                value,
547                ..
548            } => {
549                self.walk_expr(ta, *receiver)?;
550                self.walk_expr(ta, *index)?;
551                self.walk_expr(ta, *value)?;
552            }
553            TypedStmtKind::NarrowRegion { source, body, .. } => {
554                self.walk_expr(ta, *source)?;
555                self.walk_stmt(ta, *body)?;
556            }
557            TypedStmtKind::Throw { value } => self.walk_expr(ta, *value)?,
558            TypedStmtKind::Try {
559                body,
560                catches,
561                finally,
562            } => {
563                self.walk_stmt(ta, *body)?;
564                for clause in catches {
565                    self.walk_stmt(ta, clause.body)?;
566                }
567                if let Some(finally) = finally {
568                    self.walk_stmt(ta, *finally)?;
569                }
570            }
571        }
572        self.note_stmt_post(ta, id)?;
573        Ok(())
574    }
575
576    fn walk_expr(
577        &mut self,
578        ta: &TypedAst,
579        id: ExprId,
580    ) -> Result<(), crate::compiler_error::CompilerFailure> {
581        let expr = ta.try_expr(id).map_err(crate::codegen::arena_failure)?;
582        self.note_expr_pre(ta, id)
583            .map_err(|error| error.with_span(expr.span))?;
584        let _: () = match &expr.kind {
585            TypedExprKind::Call { args, .. }
586            | TypedExprKind::VirtualCall { args, .. }
587            | TypedExprKind::SuperCtorCall { args, .. }
588            | TypedExprKind::SuperMethodCall { args, .. }
589            | TypedExprKind::IntrinsicCall { args, .. } => {
590                for &arg in args {
591                    self.walk_expr(ta, arg)?;
592                }
593            }
594            TypedExprKind::CallClosure { callee, args } => {
595                self.walk_expr(ta, *callee)?;
596                for &arg in args {
597                    self.walk_expr(ta, arg)?;
598                }
599            }
600            TypedExprKind::GenericCall {
601                args, type_args, ..
602            } => {
603                self.mentioned_closure_sigs
604                    .push(super::field_guards::signature());
605                for ty in type_args {
606                    self.visit_type(ty)?;
607                }
608                for arg in args {
609                    self.walk_expr(ta, arg.expr)?;
610                }
611            }
612            TypedExprKind::Closure { body, .. } => match *body {
613                ClosureBody::Expr(expr) => self.walk_expr(ta, expr)?,
614                ClosureBody::Block(stmt) => self.walk_stmt(ta, stmt)?,
615            },
616            TypedExprKind::Binary { lhs, rhs, .. } => {
617                self.walk_expr(ta, *lhs)?;
618                self.walk_expr(ta, *rhs)?;
619            }
620            TypedExprKind::EffectThen { effect, result } => {
621                self.walk_expr(ta, *effect)?;
622                self.walk_expr(ta, *result)?;
623            }
624            TypedExprKind::Sequence { stmts, result } => {
625                for &stmt in stmts {
626                    self.walk_stmt(ta, stmt)?;
627                }
628                self.walk_expr(ta, *result)?;
629            }
630            TypedExprKind::Unary { operand, .. }
631            | TypedExprKind::TypeofTag { value: operand, .. }
632            | TypedExprKind::InstanceOf { value: operand, .. }
633            | TypedExprKind::NonNullAssert { value: operand } => {
634                self.walk_expr(ta, *operand)?;
635            }
636            TypedExprKind::MethodCall { receiver, args, .. } => {
637                self.walk_expr(ta, *receiver)?;
638                for &arg in args {
639                    self.walk_expr(ta, arg)?;
640                }
641            }
642            TypedExprKind::GenericMethodCall { receiver, args, .. } => {
643                self.walk_expr(ta, *receiver)?;
644                for arg in args {
645                    self.walk_expr(ta, arg.expr)?;
646                }
647            }
648            TypedExprKind::ObjectLiteral { members, .. } => {
649                for member in members {
650                    for expression in member.expressions() {
651                        self.walk_expr(ta, expression)?;
652                    }
653                }
654            }
655            TypedExprKind::ArrayLiteral { elements, .. } => {
656                for elem in elements {
657                    self.walk_expr(ta, elem.expr_id())?;
658                }
659            }
660            TypedExprKind::TupleLiteral { elements, .. } => {
661                for &elem in elements {
662                    self.walk_expr(ta, elem)?;
663                }
664            }
665            TypedExprKind::FieldAccess { receiver, .. }
666            | TypedExprKind::InterfacePropertyAccess { receiver, .. } => {
667                self.walk_expr(ta, *receiver)?;
668            }
669            TypedExprKind::IndexAccess { receiver, index } => {
670                self.walk_expr(ta, *receiver)?;
671                self.walk_expr(ta, *index)?;
672            }
673            TypedExprKind::Narrowed { source, inner, .. } => {
674                self.walk_expr(ta, *source)?;
675                self.walk_expr(ta, *inner)?;
676            }
677            TypedExprKind::Ternary { cond, then_, else_ } => {
678                self.walk_expr(ta, *cond)?;
679                self.walk_expr(ta, *then_)?;
680                self.walk_expr(ta, *else_)?;
681            }
682            TypedExprKind::NullishCoalesce { lhs, rhs } => {
683                self.walk_expr(ta, *lhs)?;
684                self.walk_expr(ta, *rhs)?;
685            }
686            TypedExprKind::OptionalChain { base, parts } => {
687                self.walk_expr(ta, *base)?;
688                for part in parts {
689                    match part {
690                        TypedChainPart::Index { idx, .. } => self.walk_expr(ta, *idx)?,
691                        TypedChainPart::Call { args, .. }
692                        | TypedChainPart::MethodCall { args, .. } => {
693                            for &arg in args {
694                                self.walk_expr(ta, arg)?;
695                            }
696                        }
697                        TypedChainPart::Field { .. }
698                        | TypedChainPart::InterfaceProperty { .. }
699                        | TypedChainPart::NonNull { .. } => {}
700                    }
701                }
702            }
703            TypedExprKind::PostfixUnary { target, .. } => match target {
704                PostfixTarget::Field { receiver, .. } => self.walk_expr(ta, *receiver)?,
705                PostfixTarget::Index {
706                    receiver, index, ..
707                } => {
708                    self.walk_expr(ta, *receiver)?;
709                    self.walk_expr(ta, *index)?;
710                }
711                PostfixTarget::Local { .. } | PostfixTarget::Global { .. } => {}
712            },
713            TypedExprKind::Cast { value, .. } => self.walk_expr(ta, *value)?,
714            TypedExprKind::Number(_)
715            | TypedExprKind::BigInt(_)
716            | TypedExprKind::String(_)
717            | TypedExprKind::Boolean(_)
718            | TypedExprKind::Null
719            | TypedExprKind::Undefined
720            | TypedExprKind::This
721            | TypedExprKind::Regex { .. }
722            | TypedExprKind::LocalRef { .. }
723            | TypedExprKind::LocalNarrowRef { .. }
724            | TypedExprKind::GlobalRef { .. }
725            | TypedExprKind::FunctionRef { .. }
726            | TypedExprKind::NumberEnumMember { .. }
727            | TypedExprKind::StringEnumMember { .. } => {}
728        };
729        Ok(())
730    }
731
732    fn note_stmt_pre(
733        &mut self,
734        ta: &TypedAst,
735        id: StmtId,
736    ) -> Result<(), crate::compiler_error::CompilerFailure> {
737        let stmt = ta.try_stmt(id).map_err(crate::codegen::arena_failure)?;
738        if matches!(
739            stmt.kind,
740            TypedStmtKind::Let { boxed: true, .. }
741                | TypedStmtKind::AssignLocal { boxed: true, .. }
742                | TypedStmtKind::ReboxLocal { .. }
743        ) {
744            self.note_binding_cell();
745        }
746        let _: () = match &stmt.kind {
747            TypedStmtKind::Let { ty, .. } | TypedStmtKind::Const { ty, .. } => {
748                self.visit_type_at(ty, stmt.span)?;
749            }
750            TypedStmtKind::ForOf { element_ty, .. } => {
751                self.visit_type_at(element_ty, stmt.span)?;
752            }
753            TypedStmtKind::Switch {
754                discriminant_ty,
755                cases,
756                ..
757            } => {
758                self.visit_type_at(discriminant_ty, stmt.span)?;
759                for case in cases {
760                    for value in &case.values {
761                        if let TypedSwitchValue::Enum { enum_name, .. } = value {
762                            self.dependency_usage.note_type(enum_name.clone());
763                        }
764                    }
765                }
766            }
767            TypedStmtKind::AssignLocal { target_ty, .. } => {
768                self.visit_type_at(target_ty, stmt.span)?;
769            }
770            TypedStmtKind::AssignGlobal {
771                mangled, target_ty, ..
772            } => {
773                self.dependency_usage.note_value(mangled.clone());
774                self.visit_type_at(target_ty, stmt.span)?;
775            }
776            TypedStmtKind::AssignField { receiver, name, .. } => {
777                let receiver_ty = ta
778                    .source_type(*receiver)
779                    .map_err(crate::codegen::arena_failure)?;
780                // A prelude interface's host setter, if it declares one; an
781                // unused name imports nothing.
782                if let Some(iface) = crate::codegen::host_property_iface(receiver_ty) {
783                    self.dependency_usage.note_member(crate::mangle::extend(
784                        &iface,
785                        &crate::mangle::property_setter(&name.name),
786                    ));
787                }
788                if crate::codegen::may_hold_builtin_value(receiver_ty) {
789                    self.note_record_helpers();
790                }
791                self.extra_field_names.push(name.name.clone());
792                self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Set);
793            }
794            TypedStmtKind::AssignIndex {
795                receiver, elem_ty, ..
796            } => {
797                if ta
798                    .source_type(*receiver)
799                    .map_err(crate::codegen::arena_failure)?
800                    .is_structural_object()
801                {
802                    self.note_record_helpers();
803                }
804                self.visit_type_at(elem_ty, stmt.span)?;
805                self.note_index_check();
806            }
807            TypedStmtKind::NarrowRegion { cast_info, .. } => {
808                self.visit_type_at(&cast_info.from_ty, stmt.span)?;
809                self.visit_type_at(&cast_info.to_ty, stmt.span)?;
810            }
811            TypedStmtKind::Try { catches, .. } => {
812                // A cross-package subclass used only in a catch annotation
813                // still needs its class reconstructed.
814                for clause in catches {
815                    self.visit_type_at(&clause.ty, stmt.span)?;
816                }
817            }
818            TypedStmtKind::If { .. }
819            | TypedStmtKind::While { .. }
820            | TypedStmtKind::For { .. }
821            | TypedStmtKind::DoWhile { .. }
822            | TypedStmtKind::Break
823            | TypedStmtKind::Continue
824            | TypedStmtKind::ReboxLocal { .. }
825            | TypedStmtKind::Return(_)
826            | TypedStmtKind::Expr(_)
827            | TypedStmtKind::Block(_)
828            | TypedStmtKind::Throw { .. } => {}
829        };
830        Ok(())
831    }
832
833    fn note_stmt_post(
834        &mut self,
835        ta: &TypedAst,
836        id: StmtId,
837    ) -> Result<(), crate::compiler_error::CompilerFailure> {
838        let _: () = if let TypedStmtKind::Switch { cases, .. } =
839            &ta.try_stmt(id).map_err(crate::codegen::arena_failure)?.kind
840        {
841            for case in cases {
842                for value in &case.values {
843                    match value {
844                        TypedSwitchValue::String { value, .. } => {
845                            self.string_pool.intern_text(value);
846                        }
847                        TypedSwitchValue::Enum {
848                            value: crate::EnumVariantPayload::String(text),
849                            ..
850                        } => {
851                            self.string_pool.intern_text(text);
852                        }
853                        _ => {}
854                    }
855                }
856            }
857        };
858        Ok(())
859    }
860
861    fn note_expr_pre(
862        &mut self,
863        ta: &TypedAst,
864        id: ExprId,
865    ) -> Result<(), crate::compiler_error::CompilerFailure> {
866        let expr = ta.try_expr(id).map_err(crate::codegen::arena_failure)?;
867        let has_binding_cell = match &expr.kind {
868            TypedExprKind::LocalRef { boxed: true, .. }
869            | TypedExprKind::PostfixUnary {
870                target: PostfixTarget::Local { boxed: true, .. },
871                ..
872            } => true,
873            TypedExprKind::Closure {
874                params, captured, ..
875            } => {
876                params.iter().any(|param| param.boxed)
877                    || captured.iter().any(|capture| capture.boxed)
878            }
879            _ => false,
880        };
881        if has_binding_cell {
882            self.note_binding_cell();
883        }
884        if matches!(
885            expr.kind,
886            TypedExprKind::IndexAccess { .. }
887                | TypedExprKind::Binary {
888                    op: crate::BinOp::In,
889                    ..
890                }
891        ) {
892            self.note_record_helpers();
893        }
894        if matches!(expr.kind, TypedExprKind::PostfixUnary { .. }) {
895            self.note_record_helpers();
896        }
897        self.visit_type(&expr.ty)
898            .map_err(|error| error.with_span(expr.span))?;
899        let _: () = match &expr.kind {
900            TypedExprKind::String(text) => {
901                self.string_pool.record_expr(id, text);
902            }
903            TypedExprKind::StringEnumMember {
904                enum_mangled,
905                value,
906                ..
907            } => {
908                self.string_pool.record_expr(id, value);
909                self.dependency_usage.note_type(enum_mangled.clone());
910            }
911            TypedExprKind::BigInt(digits) => {
912                self.bigint_pool.intern_digits(digits)?;
913                self.dependency_usage
914                    .collect_bigint_host_value("fromNumber");
915            }
916            TypedExprKind::GlobalRef { mangled, .. } => {
917                self.dependency_usage.note_value(mangled.clone());
918            }
919            TypedExprKind::FunctionRef { name, mangled } => {
920                self.dependency_usage.note_value(mangled.clone());
921                if self.adapter_seen.insert(mangled.clone()) {
922                    self.adapter_metas.push(AdapterMeta {
923                        name: name.name.clone(),
924                        mangled: mangled.clone(),
925                        signature: expr.ty.clone(),
926                    });
927                }
928            }
929            TypedExprKind::NumberEnumMember { enum_mangled, .. } => {
930                self.dependency_usage.note_type(enum_mangled.clone());
931            }
932            TypedExprKind::Binary { op, lhs, .. } => {
933                if let Some(name) = op.bitwise_name() {
934                    self.dependency_usage
935                        .note_value(crate::mangle::prelude(&format!("__value_{name}")));
936                    // The inline ToInt32 falls back to `x | 0` for huge values.
937                    self.dependency_usage
938                        .note_value(crate::mangle::prelude("__value_bitor"));
939                }
940                if matches!(op, BinOp::Pow) && matches!(expr.ty.peel(), Type::Number) {
941                    self.dependency_usage
942                        .note_value(crate::runtime::prelude::math::math_key("pow"));
943                }
944                if expr.ty.is_bigint() {
945                    match op {
946                        BinOp::Add => self.dependency_usage.collect_bigint_host_value("add"),
947                        BinOp::Sub => self.dependency_usage.collect_bigint_host_value("sub"),
948                        BinOp::Mul => self.dependency_usage.collect_bigint_host_value("mul"),
949                        BinOp::Div => self.dependency_usage.collect_bigint_host_value("div"),
950                        BinOp::Rem => self.dependency_usage.collect_bigint_host_value("mod"),
951                        BinOp::Pow => self.dependency_usage.collect_bigint_host_value("pow"),
952                        BinOp::Eq
953                        | BinOp::BitAnd
954                        | BinOp::BitOr
955                        | BinOp::BitXor
956                        | BinOp::Shl
957                        | BinOp::Shr
958                        | BinOp::UnsignedShr
959                        | BinOp::NotEq
960                        | BinOp::Lt
961                        | BinOp::Gt
962                        | BinOp::Le
963                        | BinOp::Ge
964                        | BinOp::And
965                        | BinOp::Or
966                        | BinOp::In
967                        | BinOp::NullishCoalesce => {}
968                    }
969                }
970                if ta
971                    .try_expr(*lhs)
972                    .map_err(crate::codegen::arena_failure)?
973                    .ty
974                    .is_bigint()
975                    && matches!(
976                        op,
977                        BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge
978                    )
979                {
980                    self.dependency_usage.collect_bigint_host_value("cmp");
981                }
982                if matches!(op, BinOp::In)
983                    && let TypedExprKind::String(name) = &ta
984                        .try_expr(*lhs)
985                        .map_err(crate::codegen::arena_failure)?
986                        .kind
987                {
988                    self.extra_field_names.push(name.clone());
989                    self.extra_field_names
990                        .push(crate::codegen::classes::accessor_getter_name(name));
991                    self.extra_field_names
992                        .push(crate::codegen::classes::accessor_setter_name(name));
993                    for kind in [AccessorKind::Get, AccessorKind::Set] {
994                        self.mentioned_closure_sigs
995                            .push(crate::codegen::classes::accessor_closure_sig(kind));
996                    }
997                }
998            }
999            TypedExprKind::Unary { op, operand } => {
1000                if matches!(op, crate::UnOp::BitNot) {
1001                    self.dependency_usage
1002                        .note_value(crate::mangle::prelude("__value_bitnot"));
1003                    self.dependency_usage
1004                        .note_value(crate::mangle::prelude("__value_bitor"));
1005                }
1006                let operand_ty = ta
1007                    .try_expr(*operand)
1008                    .map_err(crate::codegen::arena_failure)?
1009                    .ty
1010                    .peel();
1011                if matches!(op, crate::UnOp::Neg) && operand_ty.is_bigint() {
1012                    self.dependency_usage.collect_bigint_host_value("neg");
1013                }
1014                if matches!(op, crate::UnOp::Pos)
1015                    && (operand_ty.is_string_shaped()
1016                        || matches!(operand_ty.primitive_behavior(), crate::Type::Union(_)))
1017                {
1018                    self.dependency_usage.collect_number_coercion_host_value();
1019                }
1020            }
1021            TypedExprKind::Call { mangled, .. } | TypedExprKind::GenericCall { mangled, .. } => {
1022                self.dependency_usage.note_value(mangled.clone());
1023            }
1024            TypedExprKind::VirtualCall { target, tool, .. } => {
1025                self.dependency_usage
1026                    .note_value(crate::mangle::host(&target.package_name(), tool));
1027                self.dependency_usage
1028                    .note_value(crate::mangle::host(target.host_module(), "call"));
1029                if !matches!(expr.ty.peel(), Type::String) {
1030                    self.dependency_usage.collect_json_host_values();
1031                }
1032            }
1033            TypedExprKind::IntrinsicCall { kind, .. } => match kind {
1034                Intrinsic::JsonParse | Intrinsic::JsonStringify => {
1035                    self.dependency_usage.collect_json_host_values();
1036                }
1037                Intrinsic::BigIntFromString => {
1038                    self.dependency_usage
1039                        .collect_bigint_host_value("fromString");
1040                }
1041                Intrinsic::ToString => {
1042                    self.dependency_usage
1043                        .note_value(crate::mangle::prelude("__value_to_string"));
1044                }
1045                Intrinsic::ToObject => {
1046                    self.dependency_usage.note_member(crate::mangle::extend(
1047                        &crate::mangle::prelude("ObjectConstructor"),
1048                        "#toObject",
1049                    ));
1050                }
1051                Intrinsic::Assert => {}
1052            },
1053            TypedExprKind::MethodCall {
1054                receiver,
1055                iface,
1056                name,
1057                args,
1058                ..
1059            } => {
1060                self.note_method_call(
1061                    ta.source_type(*receiver)
1062                        .map_err(crate::codegen::arena_failure)?,
1063                    iface,
1064                    name,
1065                    args.len(),
1066                    &expr.ty,
1067                )?;
1068            }
1069            TypedExprKind::GenericMethodCall {
1070                receiver,
1071                iface,
1072                name,
1073                args,
1074                ..
1075            } => {
1076                self.note_method_call(
1077                    ta.source_type(*receiver)
1078                        .map_err(crate::codegen::arena_failure)?,
1079                    iface,
1080                    name,
1081                    args.len(),
1082                    &expr.ty,
1083                )?;
1084            }
1085            TypedExprKind::InterfacePropertyAccess { iface, name, .. } => {
1086                self.dependency_usage
1087                    .note_member(crate::mangle::extend(iface, &name.name));
1088            }
1089            TypedExprKind::ObjectLiteral { fields, members } => {
1090                if members
1091                    .iter()
1092                    .any(|m| matches!(m, crate::TypedObjectMember::Computed { .. }))
1093                {
1094                    self.note_record_helpers();
1095                }
1096                if members.iter().any(|member| {
1097                    matches!(
1098                        member,
1099                        crate::TypedObjectMember::Spread { .. }
1100                            | crate::TypedObjectMember::Computed { .. }
1101                    )
1102                }) {
1103                    self.dependency_usage.note_member(crate::mangle::extend(
1104                        &crate::mangle::prelude("ObjectConstructor"),
1105                        "#spread",
1106                    ));
1107                }
1108                for member in members {
1109                    if let crate::TypedObjectMember::Spread { source, .. } = member
1110                        && let Some(omitted) = ta.spread_omitted_fields.get(source)
1111                    {
1112                        self.extra_field_names.extend(omitted.iter().cloned());
1113                    }
1114                }
1115                for field in fields {
1116                    let mut source = Some(&field.source);
1117                    while let Some(TypedObjectFieldSource::Spread {
1118                        source_ty,
1119                        field_name,
1120                        fallback,
1121                        ..
1122                    }) = source
1123                    {
1124                        self.visit_type(source_ty)?;
1125                        // A spread read by name looks its field up by name and tests
1126                        // the value against the field's type, which reads names too.
1127                        self.extra_field_names.push(field_name.clone());
1128                        self.note_shape_member_names(source_ty);
1129                        source = fallback.as_deref();
1130                    }
1131                }
1132            }
1133            TypedExprKind::ArrayLiteral { element_ty, .. } => {
1134                self.visit_type(element_ty)?;
1135            }
1136            TypedExprKind::TupleLiteral { element_types, .. } => {
1137                for ty in element_types {
1138                    self.visit_type(ty)?;
1139                }
1140            }
1141            TypedExprKind::FieldAccess { receiver, name } => {
1142                self.extra_field_names.push(name.name.clone());
1143                self.note_shaped_property_access(
1144                    ta.source_type(*receiver)
1145                        .map_err(crate::codegen::arena_failure)?,
1146                    &name.name,
1147                    AccessorKind::Get,
1148                );
1149            }
1150            TypedExprKind::IndexAccess { receiver, .. } => {
1151                self.note_index_check();
1152                self.note_string_index(
1153                    ta.source_type(*receiver)
1154                        .map_err(crate::codegen::arena_failure)?,
1155                );
1156            }
1157            TypedExprKind::Closure {
1158                runtime_generics,
1159                params,
1160                captured,
1161                body,
1162                return_type,
1163                ..
1164            } => {
1165                self.closure_metas.push(ClosureMeta {
1166                    this_type: ta.closure_this.get(&id).cloned(),
1167                    self_name: ta.closure_names.get(&id).cloned(),
1168                    runtime_generics: runtime_generics.clone(),
1169                    expr_id: id,
1170                    signature: expr.ty.clone(),
1171                    captured: captured.clone(),
1172                    params: params.clone(),
1173                    body: body.clone(),
1174                    return_type: return_type.clone(),
1175                });
1176                for p in params {
1177                    self.visit_type(&p.ty)?;
1178                }
1179                self.visit_type(return_type)?;
1180            }
1181            TypedExprKind::Narrowed { cast_info, .. } => {
1182                self.visit_type(&cast_info.from_ty)?;
1183                self.visit_type(&cast_info.to_ty)?;
1184            }
1185            TypedExprKind::OptionalChain { base, parts } => {
1186                self.note_chain_parts(ta, id, *base, parts)?;
1187            }
1188            TypedExprKind::PostfixUnary { target, .. } => match target {
1189                PostfixTarget::Local { .. } => {
1190                    self.note_postfix_target(&expr.ty)?;
1191                }
1192                PostfixTarget::Global { mangled, .. } => {
1193                    self.dependency_usage.note_value(mangled.clone());
1194                    self.note_postfix_target(&expr.ty)?;
1195                }
1196                PostfixTarget::Field { receiver, name, .. } => {
1197                    self.note_postfix_target(&expr.ty)?;
1198                    let receiver_ty = ta
1199                        .source_type(*receiver)
1200                        .map_err(crate::codegen::arena_failure)?;
1201                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Get);
1202                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Set);
1203                    if let Some(iface) = crate::codegen::host_property_iface(receiver_ty) {
1204                        self.dependency_usage
1205                            .note_member(crate::mangle::extend(&iface, &name.name));
1206                        self.dependency_usage.note_member(crate::mangle::extend(
1207                            &iface,
1208                            &crate::mangle::property_setter(&name.name),
1209                        ));
1210                    }
1211                }
1212                PostfixTarget::Index { elem_ty, .. } => {
1213                    self.note_postfix_target(elem_ty)?;
1214                    self.note_index_check();
1215                }
1216            },
1217            TypedExprKind::NonNullAssert { .. } => {
1218                self.string_pool.intern_text(throw::NON_NULL_ASSERT_MESSAGE);
1219            }
1220            TypedExprKind::Cast {
1221                target_ty, check, ..
1222            } => self.note_cast(target_ty, check.as_deref())?,
1223            TypedExprKind::Regex { source, flags } => {
1224                self.string_pool.intern_text(source);
1225                self.string_pool.intern_text(flags);
1226            }
1227            TypedExprKind::CallClosure { callee, .. } => {
1228                self.dependency_usage
1229                    .note_value(crate::mangle::prelude("__value_invoke_defaults"));
1230                self.string_pool.intern_text(&cast_check::error_prefix(
1231                    ta.source_type(*callee)
1232                        .map_err(crate::codegen::arena_failure)?,
1233                ));
1234                for tag in cast_check::TYPE_TAG_STRINGS {
1235                    self.string_pool.intern_text(tag);
1236                }
1237            }
1238            TypedExprKind::InstanceOf { class, .. } => {
1239                // `ref.test (ref $Foo)` needs the class's type (and its rec group, for an
1240                // imported class) pulled into the dependency set; `expr.ty` is just `boolean`.
1241                self.visit_type(class)?;
1242            }
1243            TypedExprKind::Number(_)
1244            | TypedExprKind::Boolean(_)
1245            | TypedExprKind::Null
1246            | TypedExprKind::Undefined
1247            | TypedExprKind::This
1248            | TypedExprKind::EffectThen { .. }
1249            | TypedExprKind::Sequence { .. }
1250            | TypedExprKind::LocalRef { .. }
1251            | TypedExprKind::LocalNarrowRef { .. }
1252            | TypedExprKind::SuperCtorCall { .. }
1253            | TypedExprKind::SuperMethodCall { .. }
1254            | TypedExprKind::TypeofTag { .. }
1255            | TypedExprKind::Ternary { .. }
1256            | TypedExprKind::NullishCoalesce { .. } => {}
1257        };
1258        Ok(())
1259    }
1260
1261    /// A method call notes its member for imports and, when it dispatches
1262    /// through an object shape, the sig that dispatch builds.
1263    fn note_method_call(
1264        &mut self,
1265        receiver_ty: &Type,
1266        iface: &crate::MangledName,
1267        name: &crate::Ident,
1268        arity: usize,
1269        ret: &Type,
1270    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1271        self.dependency_usage
1272            .note_member(crate::mangle::extend(iface, &name.name));
1273        self.string_pool.intern_text("Value is not callable");
1274        self.string_pool
1275            .intern_text("Unbound function has no this receiver");
1276        self.extra_field_names.push(name.name.clone());
1277        self.note_shape_dispatch(receiver_ty, arity, ret)?;
1278
1279        Ok(())
1280    }
1281
1282    /// A method reached through an `$ObjectShape` receiver dispatches as a
1283    /// closure field, and codegen builds that closure's sig from the *call
1284    /// site* — arity plus return type — rather than from any type in scope
1285    /// (`emit_interface_method_via_shape_with_receiver_on_stack`). Nothing else
1286    /// records it: `walk_type` on an `InterfaceRef` visits type arguments only,
1287    /// and a `void` return contributes no type at all.
1288    ///
1289    /// Classes and interfaces carry mutable method payloads. A method on
1290    /// an anonymous object type is a function-typed field, so it lowers to a
1291    /// property read followed by a closure call, never to method dispatch.
1292    fn note_shape_dispatch(
1293        &mut self,
1294        receiver_ty: &Type,
1295        arity: usize,
1296        ret: &Type,
1297    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1298        self.string_pool.intern_text("Value is not callable");
1299        self.string_pool
1300            .intern_text("Unbound function has no this receiver");
1301        // An optional chain dispatches on the non-null half of its receiver.
1302        let receiver_ty = crate::typechecker::infer::narrowing::strip_nullish(receiver_ty);
1303        if !matches!(
1304            receiver_ty.peel(),
1305            Type::InterfaceRef { .. } | Type::ClassRef { .. }
1306        ) {
1307            return Ok(());
1308        }
1309        self.mentioned_closure_sigs
1310            .push(ClosureSig::of(arity, ret)?);
1311
1312        Ok(())
1313    }
1314
1315    fn note_binding_cell(&mut self) {
1316        self.string_pool
1317            .intern_text(super::function_emitter::binding_cells::UNINITIALIZED_PARAMETER_MESSAGE);
1318    }
1319
1320    /// Each step's receiver is the previous step's result, so a chain has to be
1321    /// read in order to know what any one step dispatches on.
1322    fn note_chain_parts(
1323        &mut self,
1324        ta: &TypedAst,
1325        id: ExprId,
1326        base: ExprId,
1327        parts: &[TypedChainPart],
1328    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1329        let source_types = ta.runtime_chain_types.get(&id);
1330        let mut receiver_ty = ta
1331            .source_type(base)
1332            .map_err(crate::codegen::arena_failure)?;
1333        for (index, part) in parts.iter().enumerate() {
1334            if let TypedChainPart::MethodCall { iface, name, .. }
1335            | TypedChainPart::InterfaceProperty { iface, name, .. } = part
1336            {
1337                self.string_pool.intern_text(iface.as_str());
1338                self.string_pool.intern_text(&name.name);
1339            }
1340            match part {
1341                TypedChainPart::Field {
1342                    name, result_ty, ..
1343                } => {
1344                    self.extra_field_names.push(name.name.clone());
1345                    self.visit_type(result_ty)?;
1346                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Get);
1347                }
1348                TypedChainPart::InterfaceProperty {
1349                    iface,
1350                    name,
1351                    result_ty,
1352                    span,
1353                    ..
1354                } => {
1355                    if ta.authored_call_arguments(*span).is_some()
1356                        && !crate::codegen::is_host_package(iface.as_str())
1357                    {
1358                        for helper in ["__value_member", "__value_invoke"] {
1359                            self.dependency_usage
1360                                .note_value(crate::mangle::prelude(helper));
1361                        }
1362                    }
1363                    self.extra_field_names.push(name.name.clone());
1364                    self.dependency_usage
1365                        .note_member(crate::mangle::extend(iface, &name.name));
1366                    self.visit_type(result_ty)?;
1367                }
1368                TypedChainPart::Index { result_ty, .. } => {
1369                    self.note_record_helpers();
1370                    self.visit_type(result_ty)?;
1371                    // A chain index is bounds-checked like any other, so it
1372                    // needs the same message in the pool.
1373                    self.note_index_check();
1374                    self.note_string_index(receiver_ty);
1375                }
1376                TypedChainPart::Call {
1377                    result_ty,
1378                    union_signature,
1379                    ..
1380                } => {
1381                    self.dependency_usage
1382                        .note_value(crate::mangle::prelude("__value_invoke_defaults"));
1383                    self.visit_type(result_ty)?;
1384                    if let Some(signature) = union_signature {
1385                        self.visit_type(signature)?;
1386                    }
1387                }
1388                TypedChainPart::NonNull { result_ty, .. } => {
1389                    self.visit_type(result_ty)?;
1390                    self.string_pool.intern_text(throw::NON_NULL_ASSERT_MESSAGE);
1391                }
1392                TypedChainPart::MethodCall {
1393                    iface,
1394                    name,
1395                    args,
1396                    result_ty,
1397                    span,
1398                    ..
1399                } => {
1400                    if ta.authored_call_arguments(*span).is_some()
1401                        && !crate::codegen::is_host_package(iface.as_str())
1402                    {
1403                        for helper in ["__value_member", "__value_invoke"] {
1404                            self.dependency_usage
1405                                .note_value(crate::mangle::prelude(helper));
1406                        }
1407                    }
1408                    self.extra_field_names.push(name.name.clone());
1409                    self.dependency_usage
1410                        .note_member(crate::mangle::extend(iface, &name.name));
1411                    self.visit_type(result_ty)?;
1412                    self.note_shape_dispatch(receiver_ty, args.len(), result_ty)?;
1413                }
1414            }
1415            receiver_ty = source_types.map_or_else(|| part.result_ty(), |types| &types[index + 1]);
1416        }
1417        Ok(())
1418    }
1419
1420    /// A property access on an `$ObjectShape` receiver emits every branch it
1421    /// might take: the payload-slot read (or write), a dispatch to the synthetic
1422    /// `get <prop>` / `set <prop>` method for the accessor case, and — for a
1423    /// write — the read-only-property message the getter-backed branch throws.
1424    /// The accessor branch needs its closure sig even when the program declares
1425    /// no accessor at all, since the branch is emitted on the field-name scan
1426    /// alone.
1427    ///
1428    /// The `get <prop>` / `set <prop>` `$string` globals are interned off the
1429    /// *access*, not off a declaration: whether a value is accessor-backed is a
1430    /// whole-program fact, and a library that declares an interface and reads it
1431    /// never sees the consumer's accessor implementation.
1432    fn note_shaped_property_access(&mut self, receiver_ty: &Type, prop: &str, kind: AccessorKind) {
1433        if matches!(kind, AccessorKind::Set) {
1434            self.dependency_usage.note_member(crate::mangle::extend(
1435                &crate::mangle::prelude("ObjectConstructor"),
1436                "#insertField",
1437            ));
1438        }
1439        self.mentioned_closure_sigs
1440            .push(super::field_guards::signature());
1441        if !is_shaped_receiver(receiver_ty) && !matches!(receiver_ty.peel(), Type::ClassRef { .. })
1442        {
1443            return;
1444        }
1445        self.string_pool.intern_text("Value is not callable");
1446        self.string_pool
1447            .intern_text("Unbound function has no this receiver");
1448        // Both directions need the getter's name and sig: a write scans the
1449        // `get <prop>` slot to tell a read-only property from an absent one.
1450        self.extra_field_names
1451            .push(crate::codegen::classes::accessor_getter_name(prop));
1452        self.mentioned_closure_sigs
1453            .push(crate::codegen::classes::accessor_closure_sig(
1454                AccessorKind::Get,
1455            ));
1456        if matches!(kind, AccessorKind::Set) {
1457            self.extra_field_names
1458                .push(crate::codegen::classes::accessor_setter_name(prop));
1459            self.mentioned_closure_sigs
1460                .push(crate::codegen::classes::accessor_closure_sig(
1461                    AccessorKind::Set,
1462                ));
1463            // A write also emits the read-only-property throw, for the case
1464            // where the receiver turns out to be getter-backed.
1465            self.string_pool
1466                .intern_text(throw::READ_ONLY_PROPERTY_MESSAGE);
1467        }
1468    }
1469
1470    /// `s[i]` reads through the prelude's string index helper. A chain step's
1471    /// receiver may still be typed nullable: `s?.[i]` reads the non-null part.
1472    fn note_string_index(&mut self, receiver_ty: &Type) {
1473        if crate::typechecker::infer::narrowing::strip_nullish(receiver_ty).is_string_shaped() {
1474            self.dependency_usage
1475                .note_value(crate::mangle::prelude("string_index"));
1476        }
1477    }
1478
1479    fn note_index_check(&mut self) {
1480        self.string_pool.intern_text(bounds::INDEX_OOB_MESSAGE);
1481        for ty in [Type::Array(Box::new(Type::Unknown)), Type::Uint8Array] {
1482            self.string_pool.intern_text(&cast_check::error_prefix(&ty));
1483        }
1484        for tag in cast_check::TYPE_TAG_STRINGS {
1485            self.string_pool.intern_text(tag);
1486        }
1487    }
1488
1489    fn note_postfix_target(
1490        &mut self,
1491        ty: &Type,
1492    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1493        self.dependency_usage.collect_postfix_bigint_ops(ty);
1494        self.visit_type(ty)?;
1495
1496        Ok(())
1497    }
1498}
1499
1500/// Whether a property access on this receiver lowers to the `$ObjectShape`
1501/// field-name scan, which is what carries the accessor branch. `infer_field_access`
1502/// admits a union of object shapes as well as a single one.
1503fn is_shaped_receiver(ty: &Type) -> bool {
1504    let ty = crate::typechecker::infer::narrowing::strip_nullish(ty);
1505    match ty.peel() {
1506        Type::InterfaceRef { .. } | Type::Object { .. } => true,
1507        Type::Union(members) => members
1508            .iter()
1509            .any(|m| matches!(m.peel(), Type::InterfaceRef { .. } | Type::Object { .. })),
1510        _ => false,
1511    }
1512}