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