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) && operand_ty.is_string_shaped() {
992                    self.dependency_usage.collect_number_coercion_host_value();
993                }
994            }
995            TypedExprKind::Call { mangled, .. } | TypedExprKind::GenericCall { mangled, .. } => {
996                self.dependency_usage.note_value(mangled.clone());
997            }
998            TypedExprKind::VirtualCall { target, tool, .. } => {
999                self.dependency_usage
1000                    .note_value(crate::mangle::host(&target.package_name(), tool));
1001                self.dependency_usage
1002                    .note_value(crate::mangle::host(target.host_module(), "call"));
1003                if !matches!(expr.ty.peel(), Type::String) {
1004                    self.dependency_usage.collect_json_host_values();
1005                }
1006            }
1007            TypedExprKind::IntrinsicCall { kind, .. } => match kind {
1008                Intrinsic::JsonParse | Intrinsic::JsonStringify => {
1009                    self.dependency_usage.collect_json_host_values();
1010                }
1011                Intrinsic::BigIntFromString => {
1012                    self.dependency_usage
1013                        .collect_bigint_host_value("fromString");
1014                }
1015                Intrinsic::ToString => {
1016                    self.dependency_usage
1017                        .note_value(crate::mangle::prelude("__value_to_string"));
1018                }
1019                Intrinsic::ToObject => {
1020                    self.dependency_usage.note_member(crate::mangle::extend(
1021                        &crate::mangle::prelude("ObjectConstructor"),
1022                        "#toObject",
1023                    ));
1024                }
1025                Intrinsic::Assert => {}
1026            },
1027            TypedExprKind::MethodCall {
1028                receiver,
1029                iface,
1030                name,
1031                args,
1032                ..
1033            } => {
1034                self.note_method_call(
1035                    ta.source_type(*receiver)
1036                        .map_err(crate::codegen::arena_failure)?,
1037                    iface,
1038                    name,
1039                    args.len(),
1040                    &expr.ty,
1041                )?;
1042            }
1043            TypedExprKind::GenericMethodCall {
1044                receiver,
1045                iface,
1046                name,
1047                args,
1048                ..
1049            } => {
1050                self.note_method_call(
1051                    ta.source_type(*receiver)
1052                        .map_err(crate::codegen::arena_failure)?,
1053                    iface,
1054                    name,
1055                    args.len(),
1056                    &expr.ty,
1057                )?;
1058            }
1059            TypedExprKind::InterfacePropertyAccess { iface, name, .. } => {
1060                self.dependency_usage
1061                    .note_member(crate::mangle::extend(iface, &name.name));
1062            }
1063            TypedExprKind::ObjectLiteral { fields, members } => {
1064                if members
1065                    .iter()
1066                    .any(|m| matches!(m, crate::TypedObjectMember::Computed { .. }))
1067                {
1068                    self.note_record_helpers();
1069                }
1070                if members.iter().any(|member| {
1071                    matches!(
1072                        member,
1073                        crate::TypedObjectMember::Spread { .. }
1074                            | crate::TypedObjectMember::Computed { .. }
1075                    )
1076                }) {
1077                    self.dependency_usage.note_member(crate::mangle::extend(
1078                        &crate::mangle::prelude("ObjectConstructor"),
1079                        "#spread",
1080                    ));
1081                }
1082                for member in members {
1083                    if let crate::TypedObjectMember::Spread { source, .. } = member
1084                        && let Some(omitted) = ta.spread_omitted_fields.get(source)
1085                    {
1086                        self.extra_field_names.extend(omitted.iter().cloned());
1087                    }
1088                }
1089                for field in fields {
1090                    let mut source = Some(&field.source);
1091                    while let Some(TypedObjectFieldSource::Spread {
1092                        source_ty,
1093                        field_name,
1094                        fallback,
1095                        ..
1096                    }) = source
1097                    {
1098                        self.visit_type(source_ty)?;
1099                        // A spread read by name looks its field up by name and tests
1100                        // the value against the field's type, which reads names too.
1101                        self.extra_field_names.push(field_name.clone());
1102                        self.note_shape_member_names(source_ty);
1103                        source = fallback.as_deref();
1104                    }
1105                }
1106            }
1107            TypedExprKind::ArrayLiteral { element_ty, .. } => {
1108                self.visit_type(element_ty)?;
1109            }
1110            TypedExprKind::TupleLiteral { element_types, .. } => {
1111                for ty in element_types {
1112                    self.visit_type(ty)?;
1113                }
1114            }
1115            TypedExprKind::FieldAccess { receiver, name } => {
1116                self.extra_field_names.push(name.name.clone());
1117                self.note_shaped_property_access(
1118                    ta.source_type(*receiver)
1119                        .map_err(crate::codegen::arena_failure)?,
1120                    &name.name,
1121                    AccessorKind::Get,
1122                );
1123            }
1124            TypedExprKind::IndexAccess { receiver, .. } => {
1125                self.note_index_check();
1126                self.note_string_index(
1127                    ta.source_type(*receiver)
1128                        .map_err(crate::codegen::arena_failure)?,
1129                );
1130            }
1131            TypedExprKind::Closure {
1132                runtime_generics,
1133                params,
1134                captured,
1135                body,
1136                return_type,
1137                ..
1138            } => {
1139                self.closure_metas.push(ClosureMeta {
1140                    this_type: ta.closure_this.get(&id).cloned(),
1141                    self_name: ta.closure_names.get(&id).cloned(),
1142                    runtime_generics: runtime_generics.clone(),
1143                    expr_id: id,
1144                    signature: expr.ty.clone(),
1145                    captured: captured.clone(),
1146                    params: params.clone(),
1147                    body: body.clone(),
1148                    return_type: return_type.clone(),
1149                });
1150                for p in params {
1151                    self.visit_type(&p.ty)?;
1152                }
1153                self.visit_type(return_type)?;
1154            }
1155            TypedExprKind::Narrowed { cast_info, .. } => {
1156                self.visit_type(&cast_info.from_ty)?;
1157                self.visit_type(&cast_info.to_ty)?;
1158            }
1159            TypedExprKind::OptionalChain { base, parts } => {
1160                self.note_chain_parts(ta, id, *base, parts)?;
1161            }
1162            TypedExprKind::PostfixUnary { target, .. } => match target {
1163                PostfixTarget::Local { .. } => {
1164                    self.note_postfix_target(&expr.ty)?;
1165                }
1166                PostfixTarget::Global { mangled, .. } => {
1167                    self.dependency_usage.note_value(mangled.clone());
1168                    self.note_postfix_target(&expr.ty)?;
1169                }
1170                PostfixTarget::Field { receiver, name, .. } => {
1171                    self.note_postfix_target(&expr.ty)?;
1172                    let receiver_ty = ta
1173                        .source_type(*receiver)
1174                        .map_err(crate::codegen::arena_failure)?;
1175                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Get);
1176                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Set);
1177                    if let Some(iface) = crate::codegen::host_property_iface(receiver_ty) {
1178                        self.dependency_usage
1179                            .note_member(crate::mangle::extend(&iface, &name.name));
1180                        self.dependency_usage.note_member(crate::mangle::extend(
1181                            &iface,
1182                            &crate::mangle::property_setter(&name.name),
1183                        ));
1184                    }
1185                }
1186                PostfixTarget::Index { elem_ty, .. } => {
1187                    self.note_postfix_target(elem_ty)?;
1188                    self.note_index_check();
1189                }
1190            },
1191            TypedExprKind::NonNullAssert { .. } => {
1192                self.string_pool.intern_text(throw::NON_NULL_ASSERT_MESSAGE);
1193            }
1194            TypedExprKind::Cast {
1195                target_ty, check, ..
1196            } => {
1197                self.string_pool
1198                    .intern_text(&cast_check::error_prefix(target_ty));
1199                for tag in cast_check::TYPE_TAG_STRINGS {
1200                    self.string_pool.intern_text(tag);
1201                }
1202                self.visit_type(target_ty)?;
1203                if let Some(check) = check {
1204                    self.visit_type(check)?;
1205                    self.note_shape_member_names(check);
1206                }
1207            }
1208            TypedExprKind::Regex { source, flags } => {
1209                self.string_pool.intern_text(source);
1210                self.string_pool.intern_text(flags);
1211            }
1212            TypedExprKind::CallClosure { callee, .. } => {
1213                self.dependency_usage
1214                    .note_value(crate::mangle::prelude("__value_invoke_defaults"));
1215                self.string_pool.intern_text(&cast_check::error_prefix(
1216                    ta.source_type(*callee)
1217                        .map_err(crate::codegen::arena_failure)?,
1218                ));
1219                for tag in cast_check::TYPE_TAG_STRINGS {
1220                    self.string_pool.intern_text(tag);
1221                }
1222            }
1223            TypedExprKind::InstanceOf { class, .. } => {
1224                // `ref.test (ref $Foo)` needs the class's type (and its rec group, for an
1225                // imported class) pulled into the dependency set; `expr.ty` is just `boolean`.
1226                self.visit_type(class)?;
1227            }
1228            TypedExprKind::Number(_)
1229            | TypedExprKind::Boolean(_)
1230            | TypedExprKind::Null
1231            | TypedExprKind::Undefined
1232            | TypedExprKind::This
1233            | TypedExprKind::EffectThen { .. }
1234            | TypedExprKind::Sequence { .. }
1235            | TypedExprKind::LocalRef { .. }
1236            | TypedExprKind::LocalNarrowRef { .. }
1237            | TypedExprKind::SuperCtorCall { .. }
1238            | TypedExprKind::SuperMethodCall { .. }
1239            | TypedExprKind::TypeofTag { .. }
1240            | TypedExprKind::Ternary { .. }
1241            | TypedExprKind::NullishCoalesce { .. } => {}
1242        };
1243        Ok(())
1244    }
1245
1246    /// A method call notes its member for imports and, when it dispatches
1247    /// through an object shape, the sig that dispatch builds.
1248    fn note_method_call(
1249        &mut self,
1250        receiver_ty: &Type,
1251        iface: &crate::MangledName,
1252        name: &crate::Ident,
1253        arity: usize,
1254        ret: &Type,
1255    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1256        self.dependency_usage
1257            .note_member(crate::mangle::extend(iface, &name.name));
1258        self.string_pool.intern_text("Value is not callable");
1259        self.string_pool
1260            .intern_text("Unbound function has no this receiver");
1261        self.extra_field_names.push(name.name.clone());
1262        self.note_shape_dispatch(receiver_ty, arity, ret)?;
1263
1264        Ok(())
1265    }
1266
1267    /// A method reached through an `$ObjectShape` receiver dispatches as a
1268    /// closure field, and codegen builds that closure's sig from the *call
1269    /// site* — arity plus return type — rather than from any type in scope
1270    /// (`emit_interface_method_via_shape_with_receiver_on_stack`). Nothing else
1271    /// records it: `walk_type` on an `InterfaceRef` visits type arguments only,
1272    /// and a `void` return contributes no type at all.
1273    ///
1274    /// Classes and interfaces carry mutable method payloads. A method on
1275    /// an anonymous object type is a function-typed field, so it lowers to a
1276    /// property read followed by a closure call, never to method dispatch.
1277    fn note_shape_dispatch(
1278        &mut self,
1279        receiver_ty: &Type,
1280        arity: usize,
1281        ret: &Type,
1282    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1283        self.string_pool.intern_text("Value is not callable");
1284        self.string_pool
1285            .intern_text("Unbound function has no this receiver");
1286        // An optional chain dispatches on the non-null half of its receiver.
1287        let receiver_ty = crate::typechecker::infer::narrowing::strip_nullish(receiver_ty);
1288        if !matches!(
1289            receiver_ty.peel(),
1290            Type::InterfaceRef { .. } | Type::ClassRef { .. }
1291        ) {
1292            return Ok(());
1293        }
1294        self.mentioned_closure_sigs
1295            .push(ClosureSig::of(arity, ret)?);
1296
1297        Ok(())
1298    }
1299
1300    fn note_binding_cell(&mut self) {
1301        self.string_pool
1302            .intern_text(super::function_emitter::binding_cells::UNINITIALIZED_PARAMETER_MESSAGE);
1303    }
1304
1305    /// Each step's receiver is the previous step's result, so a chain has to be
1306    /// read in order to know what any one step dispatches on.
1307    fn note_chain_parts(
1308        &mut self,
1309        ta: &TypedAst,
1310        id: ExprId,
1311        base: ExprId,
1312        parts: &[TypedChainPart],
1313    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1314        let source_types = ta.runtime_chain_types.get(&id);
1315        let mut receiver_ty = ta
1316            .source_type(base)
1317            .map_err(crate::codegen::arena_failure)?;
1318        for (index, part) in parts.iter().enumerate() {
1319            if let TypedChainPart::MethodCall { iface, name, .. }
1320            | TypedChainPart::InterfaceProperty { iface, name, .. } = part
1321            {
1322                self.string_pool.intern_text(iface.as_str());
1323                self.string_pool.intern_text(&name.name);
1324            }
1325            match part {
1326                TypedChainPart::Field {
1327                    name, result_ty, ..
1328                } => {
1329                    self.extra_field_names.push(name.name.clone());
1330                    self.visit_type(result_ty)?;
1331                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Get);
1332                }
1333                TypedChainPart::InterfaceProperty {
1334                    iface,
1335                    name,
1336                    result_ty,
1337                    span,
1338                    ..
1339                } => {
1340                    if ta.authored_call_arguments(*span).is_some()
1341                        && !crate::codegen::is_host_package(iface.as_str())
1342                    {
1343                        for helper in ["__value_member", "__value_invoke"] {
1344                            self.dependency_usage
1345                                .note_value(crate::mangle::prelude(helper));
1346                        }
1347                    }
1348                    self.extra_field_names.push(name.name.clone());
1349                    self.dependency_usage
1350                        .note_member(crate::mangle::extend(iface, &name.name));
1351                    self.visit_type(result_ty)?;
1352                }
1353                TypedChainPart::Index { result_ty, .. } => {
1354                    self.note_record_helpers();
1355                    self.visit_type(result_ty)?;
1356                    // A chain index is bounds-checked like any other, so it
1357                    // needs the same message in the pool.
1358                    self.note_index_check();
1359                    self.note_string_index(receiver_ty);
1360                }
1361                TypedChainPart::Call {
1362                    result_ty,
1363                    union_signature,
1364                    ..
1365                } => {
1366                    self.dependency_usage
1367                        .note_value(crate::mangle::prelude("__value_invoke_defaults"));
1368                    self.visit_type(result_ty)?;
1369                    if let Some(signature) = union_signature {
1370                        self.visit_type(signature)?;
1371                    }
1372                }
1373                TypedChainPart::NonNull { result_ty, .. } => {
1374                    self.visit_type(result_ty)?;
1375                    self.string_pool.intern_text(throw::NON_NULL_ASSERT_MESSAGE);
1376                }
1377                TypedChainPart::MethodCall {
1378                    iface,
1379                    name,
1380                    args,
1381                    result_ty,
1382                    span,
1383                    ..
1384                } => {
1385                    if ta.authored_call_arguments(*span).is_some()
1386                        && !crate::codegen::is_host_package(iface.as_str())
1387                    {
1388                        for helper in ["__value_member", "__value_invoke"] {
1389                            self.dependency_usage
1390                                .note_value(crate::mangle::prelude(helper));
1391                        }
1392                    }
1393                    self.extra_field_names.push(name.name.clone());
1394                    self.dependency_usage
1395                        .note_member(crate::mangle::extend(iface, &name.name));
1396                    self.visit_type(result_ty)?;
1397                    self.note_shape_dispatch(receiver_ty, args.len(), result_ty)?;
1398                }
1399            }
1400            receiver_ty = source_types.map_or_else(|| part.result_ty(), |types| &types[index + 1]);
1401        }
1402        Ok(())
1403    }
1404
1405    /// A property access on an `$ObjectShape` receiver emits every branch it
1406    /// might take: the payload-slot read (or write), a dispatch to the synthetic
1407    /// `get <prop>` / `set <prop>` method for the accessor case, and — for a
1408    /// write — the read-only-property message the getter-backed branch throws.
1409    /// The accessor branch needs its closure sig even when the program declares
1410    /// no accessor at all, since the branch is emitted on the field-name scan
1411    /// alone.
1412    ///
1413    /// The `get <prop>` / `set <prop>` `$string` globals are interned off the
1414    /// *access*, not off a declaration: whether a value is accessor-backed is a
1415    /// whole-program fact, and a library that declares an interface and reads it
1416    /// never sees the consumer's accessor implementation.
1417    fn note_shaped_property_access(&mut self, receiver_ty: &Type, prop: &str, kind: AccessorKind) {
1418        if matches!(kind, AccessorKind::Set) {
1419            self.dependency_usage.note_member(crate::mangle::extend(
1420                &crate::mangle::prelude("ObjectConstructor"),
1421                "#insertField",
1422            ));
1423        }
1424        self.mentioned_closure_sigs
1425            .push(super::field_guards::signature());
1426        if !is_shaped_receiver(receiver_ty) && !matches!(receiver_ty.peel(), Type::ClassRef { .. })
1427        {
1428            return;
1429        }
1430        self.string_pool.intern_text("Value is not callable");
1431        self.string_pool
1432            .intern_text("Unbound function has no this receiver");
1433        // Both directions need the getter's name and sig: a write scans the
1434        // `get <prop>` slot to tell a read-only property from an absent one.
1435        self.extra_field_names
1436            .push(crate::codegen::classes::accessor_getter_name(prop));
1437        self.mentioned_closure_sigs
1438            .push(crate::codegen::classes::accessor_closure_sig(
1439                AccessorKind::Get,
1440            ));
1441        if matches!(kind, AccessorKind::Set) {
1442            self.extra_field_names
1443                .push(crate::codegen::classes::accessor_setter_name(prop));
1444            self.mentioned_closure_sigs
1445                .push(crate::codegen::classes::accessor_closure_sig(
1446                    AccessorKind::Set,
1447                ));
1448            // A write also emits the read-only-property throw, for the case
1449            // where the receiver turns out to be getter-backed.
1450            self.string_pool
1451                .intern_text(throw::READ_ONLY_PROPERTY_MESSAGE);
1452        }
1453    }
1454
1455    /// `s[i]` reads through the prelude's string index helper. A chain step's
1456    /// receiver may still be typed nullable: `s?.[i]` reads the non-null part.
1457    fn note_string_index(&mut self, receiver_ty: &Type) {
1458        if crate::typechecker::infer::narrowing::strip_nullish(receiver_ty).is_string_shaped() {
1459            self.dependency_usage
1460                .note_value(crate::mangle::prelude("string_index"));
1461        }
1462    }
1463
1464    fn note_index_check(&mut self) {
1465        self.string_pool.intern_text(bounds::INDEX_OOB_MESSAGE);
1466        for ty in [Type::Array(Box::new(Type::Unknown)), Type::Uint8Array] {
1467            self.string_pool.intern_text(&cast_check::error_prefix(&ty));
1468        }
1469        for tag in cast_check::TYPE_TAG_STRINGS {
1470            self.string_pool.intern_text(tag);
1471        }
1472    }
1473
1474    fn note_postfix_target(
1475        &mut self,
1476        ty: &Type,
1477    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1478        self.dependency_usage.collect_postfix_bigint_ops(ty);
1479        self.visit_type(ty)?;
1480
1481        Ok(())
1482    }
1483}
1484
1485/// Whether a property access on this receiver lowers to the `$ObjectShape`
1486/// field-name scan, which is what carries the accessor branch. `infer_field_access`
1487/// admits a union of object shapes as well as a single one.
1488fn is_shaped_receiver(ty: &Type) -> bool {
1489    let ty = crate::typechecker::infer::narrowing::strip_nullish(ty);
1490    match ty.peel() {
1491        Type::InterfaceRef { .. } | Type::Object { .. } => true,
1492        Type::Union(members) => members
1493            .iter()
1494            .any(|m| matches!(m.peel(), Type::InterfaceRef { .. } | Type::Object { .. })),
1495        _ => false,
1496    }
1497}