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::McpCall { 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                self.extra_field_names.push(name.name.clone());
755                self.note_shaped_property_access(
756                    ta.source_type(*receiver)
757                        .map_err(crate::codegen::arena_failure)?,
758                    &name.name,
759                    AccessorKind::Set,
760                );
761            }
762            TypedStmtKind::AssignIndex {
763                receiver, elem_ty, ..
764            } => {
765                if ta
766                    .source_type(*receiver)
767                    .map_err(crate::codegen::arena_failure)?
768                    .is_structural_object()
769                {
770                    self.note_record_helpers();
771                }
772                self.visit_type_at(elem_ty, stmt.span)?;
773                self.note_index_check();
774            }
775            TypedStmtKind::NarrowRegion { cast_info, .. } => {
776                self.visit_type_at(&cast_info.from_ty, stmt.span)?;
777                self.visit_type_at(&cast_info.to_ty, stmt.span)?;
778            }
779            TypedStmtKind::Try { catches, .. } => {
780                // A cross-package subclass used only in a catch annotation
781                // still needs its class reconstructed.
782                for clause in catches {
783                    self.visit_type_at(&clause.ty, stmt.span)?;
784                }
785            }
786            TypedStmtKind::If { .. }
787            | TypedStmtKind::While { .. }
788            | TypedStmtKind::For { .. }
789            | TypedStmtKind::DoWhile { .. }
790            | TypedStmtKind::Break
791            | TypedStmtKind::Continue
792            | TypedStmtKind::ReboxLocal { .. }
793            | TypedStmtKind::Return(_)
794            | TypedStmtKind::Expr(_)
795            | TypedStmtKind::Block(_)
796            | TypedStmtKind::Throw { .. } => {}
797        };
798        Ok(())
799    }
800
801    fn note_stmt_post(
802        &mut self,
803        ta: &TypedAst,
804        id: StmtId,
805    ) -> Result<(), crate::compiler_error::CompilerFailure> {
806        let _: () = if let TypedStmtKind::Switch { cases, .. } =
807            &ta.try_stmt(id).map_err(crate::codegen::arena_failure)?.kind
808        {
809            for case in cases {
810                for value in &case.values {
811                    match value {
812                        TypedSwitchValue::String { value, .. } => {
813                            self.string_pool.intern_text(value);
814                        }
815                        TypedSwitchValue::Enum {
816                            value: crate::EnumVariantPayload::String(text),
817                            ..
818                        } => {
819                            self.string_pool.intern_text(text);
820                        }
821                        _ => {}
822                    }
823                }
824            }
825        };
826        Ok(())
827    }
828
829    fn note_expr_pre(
830        &mut self,
831        ta: &TypedAst,
832        id: ExprId,
833    ) -> Result<(), crate::compiler_error::CompilerFailure> {
834        let expr = ta.try_expr(id).map_err(crate::codegen::arena_failure)?;
835        let has_binding_cell = match &expr.kind {
836            TypedExprKind::LocalRef { boxed: true, .. }
837            | TypedExprKind::PostfixUnary {
838                target: PostfixTarget::Local { boxed: true, .. },
839                ..
840            } => true,
841            TypedExprKind::Closure {
842                params, captured, ..
843            } => {
844                params.iter().any(|param| param.boxed)
845                    || captured.iter().any(|capture| capture.boxed)
846            }
847            _ => false,
848        };
849        if has_binding_cell {
850            self.note_binding_cell();
851        }
852        if matches!(
853            expr.kind,
854            TypedExprKind::IndexAccess { .. }
855                | TypedExprKind::Binary {
856                    op: crate::BinOp::In,
857                    ..
858                }
859        ) {
860            self.note_record_helpers();
861        }
862        if matches!(expr.kind, TypedExprKind::PostfixUnary { .. }) {
863            self.note_record_helpers();
864        }
865        self.visit_type(&expr.ty)
866            .map_err(|error| error.with_span(expr.span))?;
867        let _: () = match &expr.kind {
868            TypedExprKind::String(text) => {
869                self.string_pool.record_expr(id, text);
870            }
871            TypedExprKind::StringEnumMember {
872                enum_mangled,
873                value,
874                ..
875            } => {
876                self.string_pool.record_expr(id, value);
877                self.dependency_usage.note_type(enum_mangled.clone());
878            }
879            TypedExprKind::BigInt(digits) => {
880                self.bigint_pool.intern_digits(digits)?;
881                self.dependency_usage
882                    .collect_bigint_host_value("fromNumber");
883            }
884            TypedExprKind::GlobalRef { mangled, .. } => {
885                self.dependency_usage.note_value(mangled.clone());
886            }
887            TypedExprKind::FunctionRef { name, mangled } => {
888                self.dependency_usage.note_value(mangled.clone());
889                if self.adapter_seen.insert(mangled.clone()) {
890                    self.adapter_metas.push(AdapterMeta {
891                        name: name.name.clone(),
892                        mangled: mangled.clone(),
893                        signature: expr.ty.clone(),
894                    });
895                }
896            }
897            TypedExprKind::NumberEnumMember { enum_mangled, .. } => {
898                self.dependency_usage.note_type(enum_mangled.clone());
899            }
900            TypedExprKind::Binary { op, lhs, .. } => {
901                if let Some(name) = op.bitwise_name() {
902                    self.dependency_usage
903                        .note_value(crate::mangle::prelude(&format!("__value_{name}")));
904                    // The inline ToInt32 falls back to `x | 0` for huge values.
905                    self.dependency_usage
906                        .note_value(crate::mangle::prelude("__value_bitor"));
907                }
908                if matches!(op, BinOp::Pow) && matches!(expr.ty.peel(), Type::Number) {
909                    self.dependency_usage
910                        .note_value(crate::runtime::prelude::math::math_key("pow"));
911                }
912                if expr.ty.is_bigint() {
913                    match op {
914                        BinOp::Add => self.dependency_usage.collect_bigint_host_value("add"),
915                        BinOp::Sub => self.dependency_usage.collect_bigint_host_value("sub"),
916                        BinOp::Mul => self.dependency_usage.collect_bigint_host_value("mul"),
917                        BinOp::Div => self.dependency_usage.collect_bigint_host_value("div"),
918                        BinOp::Rem => self.dependency_usage.collect_bigint_host_value("mod"),
919                        BinOp::Pow => self.dependency_usage.collect_bigint_host_value("pow"),
920                        BinOp::Eq
921                        | BinOp::BitAnd
922                        | BinOp::BitOr
923                        | BinOp::BitXor
924                        | BinOp::Shl
925                        | BinOp::Shr
926                        | BinOp::UnsignedShr
927                        | BinOp::NotEq
928                        | BinOp::Lt
929                        | BinOp::Gt
930                        | BinOp::Le
931                        | BinOp::Ge
932                        | BinOp::And
933                        | BinOp::Or
934                        | BinOp::In
935                        | BinOp::NullishCoalesce => {}
936                    }
937                }
938                if ta
939                    .try_expr(*lhs)
940                    .map_err(crate::codegen::arena_failure)?
941                    .ty
942                    .is_bigint()
943                    && matches!(
944                        op,
945                        BinOp::Eq | BinOp::NotEq | BinOp::Lt | BinOp::Gt | BinOp::Le | BinOp::Ge
946                    )
947                {
948                    self.dependency_usage.collect_bigint_host_value("cmp");
949                }
950                if matches!(op, BinOp::In)
951                    && let TypedExprKind::String(name) = &ta
952                        .try_expr(*lhs)
953                        .map_err(crate::codegen::arena_failure)?
954                        .kind
955                {
956                    self.extra_field_names.push(name.clone());
957                    self.extra_field_names
958                        .push(crate::codegen::classes::accessor_getter_name(name));
959                    self.extra_field_names
960                        .push(crate::codegen::classes::accessor_setter_name(name));
961                    for kind in [AccessorKind::Get, AccessorKind::Set] {
962                        self.mentioned_closure_sigs
963                            .push(crate::codegen::classes::accessor_closure_sig(kind));
964                    }
965                }
966            }
967            TypedExprKind::Unary { op, operand } => {
968                if matches!(op, crate::UnOp::BitNot) {
969                    self.dependency_usage
970                        .note_value(crate::mangle::prelude("__value_bitnot"));
971                    self.dependency_usage
972                        .note_value(crate::mangle::prelude("__value_bitor"));
973                }
974                let operand_ty = ta
975                    .try_expr(*operand)
976                    .map_err(crate::codegen::arena_failure)?
977                    .ty
978                    .peel();
979                if matches!(op, crate::UnOp::Neg) && operand_ty.is_bigint() {
980                    self.dependency_usage.collect_bigint_host_value("neg");
981                }
982                if matches!(op, crate::UnOp::Pos) && operand_ty.is_string_shaped() {
983                    self.dependency_usage.collect_number_coercion_host_value();
984                }
985            }
986            TypedExprKind::Call { mangled, .. } | TypedExprKind::GenericCall { mangled, .. } => {
987                self.dependency_usage.note_value(mangled.clone());
988            }
989            TypedExprKind::McpCall { server, tool, .. } => {
990                self.dependency_usage
991                    .note_value(crate::mangle::host(&format!("@mcp/{server}"), tool));
992                self.dependency_usage
993                    .note_value(crate::mangle::host(crate::runtime::MCP_MODULE_NAME, "call"));
994                if !matches!(expr.ty.peel(), Type::String) {
995                    self.dependency_usage.collect_json_host_values();
996                }
997            }
998            TypedExprKind::IntrinsicCall { kind, .. } => match kind {
999                Intrinsic::JsonParse | Intrinsic::JsonStringify => {
1000                    self.dependency_usage.collect_json_host_values();
1001                }
1002                Intrinsic::BigIntFromString => {
1003                    self.dependency_usage
1004                        .collect_bigint_host_value("fromString");
1005                }
1006                Intrinsic::ToString => {
1007                    self.dependency_usage
1008                        .note_value(crate::mangle::prelude("__value_to_string"));
1009                }
1010                Intrinsic::Assert => {}
1011            },
1012            TypedExprKind::MethodCall {
1013                receiver,
1014                iface,
1015                name,
1016                args,
1017                ..
1018            } => {
1019                self.note_method_call(
1020                    ta.source_type(*receiver)
1021                        .map_err(crate::codegen::arena_failure)?,
1022                    iface,
1023                    name,
1024                    args.len(),
1025                    &expr.ty,
1026                )?;
1027            }
1028            TypedExprKind::GenericMethodCall {
1029                receiver,
1030                iface,
1031                name,
1032                args,
1033                ..
1034            } => {
1035                self.note_method_call(
1036                    ta.source_type(*receiver)
1037                        .map_err(crate::codegen::arena_failure)?,
1038                    iface,
1039                    name,
1040                    args.len(),
1041                    &expr.ty,
1042                )?;
1043            }
1044            TypedExprKind::InterfacePropertyAccess { iface, name, .. } => {
1045                self.dependency_usage
1046                    .note_member(crate::mangle::extend(iface, &name.name));
1047            }
1048            TypedExprKind::ObjectLiteral { fields, members } => {
1049                if members
1050                    .iter()
1051                    .any(|m| matches!(m, crate::TypedObjectMember::Computed { .. }))
1052                {
1053                    self.note_record_helpers();
1054                }
1055                if members.iter().any(|member| {
1056                    matches!(
1057                        member,
1058                        crate::TypedObjectMember::Spread { .. }
1059                            | crate::TypedObjectMember::Computed { .. }
1060                    )
1061                }) {
1062                    self.dependency_usage.note_member(crate::mangle::extend(
1063                        &crate::mangle::prelude("ObjectConstructor"),
1064                        "#spread",
1065                    ));
1066                }
1067                for member in members {
1068                    if let crate::TypedObjectMember::Spread { source, .. } = member
1069                        && let Some(omitted) = ta.spread_omitted_fields.get(source)
1070                    {
1071                        self.extra_field_names.extend(omitted.iter().cloned());
1072                    }
1073                }
1074                for field in fields {
1075                    let mut source = Some(&field.source);
1076                    while let Some(TypedObjectFieldSource::Spread {
1077                        source_ty,
1078                        field_name,
1079                        fallback,
1080                        ..
1081                    }) = source
1082                    {
1083                        self.visit_type(source_ty)?;
1084                        // A spread read by name looks its field up by name and tests
1085                        // the value against the field's type, which reads names too.
1086                        self.extra_field_names.push(field_name.clone());
1087                        self.note_shape_member_names(source_ty);
1088                        source = fallback.as_deref();
1089                    }
1090                }
1091            }
1092            TypedExprKind::ArrayLiteral { element_ty, .. } => {
1093                self.visit_type(element_ty)?;
1094            }
1095            TypedExprKind::TupleLiteral { element_types, .. } => {
1096                for ty in element_types {
1097                    self.visit_type(ty)?;
1098                }
1099            }
1100            TypedExprKind::FieldAccess { receiver, name } => {
1101                self.extra_field_names.push(name.name.clone());
1102                self.note_shaped_property_access(
1103                    ta.source_type(*receiver)
1104                        .map_err(crate::codegen::arena_failure)?,
1105                    &name.name,
1106                    AccessorKind::Get,
1107                );
1108            }
1109            TypedExprKind::IndexAccess { .. } => {
1110                self.note_index_check();
1111            }
1112            TypedExprKind::Closure {
1113                runtime_generics,
1114                params,
1115                captured,
1116                body,
1117                return_type,
1118                ..
1119            } => {
1120                self.closure_metas.push(ClosureMeta {
1121                    this_type: ta.closure_this.get(&id).cloned(),
1122                    self_name: ta.closure_names.get(&id).cloned(),
1123                    runtime_generics: runtime_generics.clone(),
1124                    expr_id: id,
1125                    signature: expr.ty.clone(),
1126                    captured: captured.clone(),
1127                    params: params.clone(),
1128                    body: body.clone(),
1129                    return_type: return_type.clone(),
1130                });
1131                for p in params {
1132                    self.visit_type(&p.ty)?;
1133                }
1134                self.visit_type(return_type)?;
1135            }
1136            TypedExprKind::Narrowed { cast_info, .. } => {
1137                self.visit_type(&cast_info.from_ty)?;
1138                self.visit_type(&cast_info.to_ty)?;
1139            }
1140            TypedExprKind::OptionalChain { base, parts } => {
1141                self.note_chain_parts(ta, id, *base, parts)?;
1142            }
1143            TypedExprKind::PostfixUnary { target, .. } => match target {
1144                PostfixTarget::Local { .. } => {
1145                    self.note_postfix_target(&expr.ty)?;
1146                }
1147                PostfixTarget::Global { mangled, .. } => {
1148                    self.dependency_usage.note_value(mangled.clone());
1149                    self.note_postfix_target(&expr.ty)?;
1150                }
1151                PostfixTarget::Field { receiver, name, .. } => {
1152                    self.note_postfix_target(&expr.ty)?;
1153                    let receiver_ty = ta
1154                        .source_type(*receiver)
1155                        .map_err(crate::codegen::arena_failure)?;
1156                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Get);
1157                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Set);
1158                }
1159                PostfixTarget::Index { elem_ty, .. } => {
1160                    self.note_postfix_target(elem_ty)?;
1161                    self.note_index_check();
1162                }
1163            },
1164            TypedExprKind::NonNullAssert { .. } => {
1165                self.string_pool.intern_text(throw::NON_NULL_ASSERT_MESSAGE);
1166            }
1167            TypedExprKind::Cast {
1168                target_ty, check, ..
1169            } => {
1170                self.string_pool
1171                    .intern_text(&cast_check::error_prefix(target_ty));
1172                for tag in cast_check::TYPE_TAG_STRINGS {
1173                    self.string_pool.intern_text(tag);
1174                }
1175                self.visit_type(target_ty)?;
1176                if let Some(check) = check {
1177                    self.visit_type(check)?;
1178                    self.note_shape_member_names(check);
1179                }
1180            }
1181            TypedExprKind::Regex { source, flags } => {
1182                self.string_pool.intern_text(source);
1183                self.string_pool.intern_text(flags);
1184            }
1185            TypedExprKind::CallClosure { callee, .. } => {
1186                self.dependency_usage
1187                    .note_value(crate::mangle::prelude("__value_invoke_defaults"));
1188                self.string_pool.intern_text(&cast_check::error_prefix(
1189                    ta.source_type(*callee)
1190                        .map_err(crate::codegen::arena_failure)?,
1191                ));
1192                for tag in cast_check::TYPE_TAG_STRINGS {
1193                    self.string_pool.intern_text(tag);
1194                }
1195            }
1196            TypedExprKind::InstanceOf { class, .. } => {
1197                // `ref.test (ref $Foo)` needs the class's type (and its rec group, for an
1198                // imported class) pulled into the dependency set; `expr.ty` is just `boolean`.
1199                self.visit_type(class)?;
1200            }
1201            TypedExprKind::Number(_)
1202            | TypedExprKind::Boolean(_)
1203            | TypedExprKind::Null
1204            | TypedExprKind::Undefined
1205            | TypedExprKind::This
1206            | TypedExprKind::EffectThen { .. }
1207            | TypedExprKind::Sequence { .. }
1208            | TypedExprKind::LocalRef { .. }
1209            | TypedExprKind::LocalNarrowRef { .. }
1210            | TypedExprKind::SuperCtorCall { .. }
1211            | TypedExprKind::SuperMethodCall { .. }
1212            | TypedExprKind::TypeofTag { .. }
1213            | TypedExprKind::Ternary { .. }
1214            | TypedExprKind::NullishCoalesce { .. } => {}
1215        };
1216        Ok(())
1217    }
1218
1219    /// A method call notes its member for imports and, when it dispatches
1220    /// through an object shape, the sig that dispatch builds.
1221    fn note_method_call(
1222        &mut self,
1223        receiver_ty: &Type,
1224        iface: &crate::MangledName,
1225        name: &crate::Ident,
1226        arity: usize,
1227        ret: &Type,
1228    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1229        self.dependency_usage
1230            .note_member(crate::mangle::extend(iface, &name.name));
1231        self.string_pool.intern_text("Value is not callable");
1232        self.string_pool
1233            .intern_text("Unbound function has no this receiver");
1234        self.extra_field_names.push(name.name.clone());
1235        self.note_shape_dispatch(receiver_ty, arity, ret)?;
1236
1237        Ok(())
1238    }
1239
1240    /// A method reached through an `$ObjectShape` receiver dispatches as a
1241    /// closure field, and codegen builds that closure's sig from the *call
1242    /// site* — arity plus return type — rather than from any type in scope
1243    /// (`emit_interface_method_via_shape_with_receiver_on_stack`). Nothing else
1244    /// records it: `walk_type` on an `InterfaceRef` visits type arguments only,
1245    /// and a `void` return contributes no type at all.
1246    ///
1247    /// Classes and interfaces carry mutable method payloads. A method on
1248    /// an anonymous object type is a function-typed field, so it lowers to a
1249    /// property read followed by a closure call, never to method dispatch.
1250    fn note_shape_dispatch(
1251        &mut self,
1252        receiver_ty: &Type,
1253        arity: usize,
1254        ret: &Type,
1255    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1256        self.string_pool.intern_text("Value is not callable");
1257        self.string_pool
1258            .intern_text("Unbound function has no this receiver");
1259        // An optional chain dispatches on the non-null half of its receiver.
1260        let receiver_ty = crate::typechecker::infer::narrowing::strip_nullish(receiver_ty);
1261        if !matches!(
1262            receiver_ty.peel(),
1263            Type::InterfaceRef { .. } | Type::ClassRef { .. }
1264        ) {
1265            return Ok(());
1266        }
1267        self.mentioned_closure_sigs
1268            .push(ClosureSig::of(arity, ret)?);
1269
1270        Ok(())
1271    }
1272
1273    fn note_binding_cell(&mut self) {
1274        self.string_pool
1275            .intern_text(super::function_emitter::binding_cells::UNINITIALIZED_PARAMETER_MESSAGE);
1276    }
1277
1278    /// Each step's receiver is the previous step's result, so a chain has to be
1279    /// read in order to know what any one step dispatches on.
1280    fn note_chain_parts(
1281        &mut self,
1282        ta: &TypedAst,
1283        id: ExprId,
1284        base: ExprId,
1285        parts: &[TypedChainPart],
1286    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1287        let source_types = ta.runtime_chain_types.get(&id);
1288        let mut receiver_ty = ta
1289            .source_type(base)
1290            .map_err(crate::codegen::arena_failure)?;
1291        for (index, part) in parts.iter().enumerate() {
1292            if let TypedChainPart::MethodCall { iface, name, .. }
1293            | TypedChainPart::InterfaceProperty { iface, name, .. } = part
1294            {
1295                self.string_pool.intern_text(iface.as_str());
1296                self.string_pool.intern_text(&name.name);
1297            }
1298            match part {
1299                TypedChainPart::Field {
1300                    name, result_ty, ..
1301                } => {
1302                    self.extra_field_names.push(name.name.clone());
1303                    self.visit_type(result_ty)?;
1304                    self.note_shaped_property_access(receiver_ty, &name.name, AccessorKind::Get);
1305                }
1306                TypedChainPart::InterfaceProperty {
1307                    iface,
1308                    name,
1309                    result_ty,
1310                    span,
1311                    ..
1312                } => {
1313                    if ta.authored_call_arguments(*span).is_some()
1314                        && !crate::codegen::is_host_package(iface.as_str())
1315                    {
1316                        for helper in ["__value_member", "__value_invoke"] {
1317                            self.dependency_usage
1318                                .note_value(crate::mangle::prelude(helper));
1319                        }
1320                    }
1321                    self.extra_field_names.push(name.name.clone());
1322                    self.dependency_usage
1323                        .note_member(crate::mangle::extend(iface, &name.name));
1324                    self.visit_type(result_ty)?;
1325                }
1326                TypedChainPart::Index { result_ty, .. } => {
1327                    self.note_record_helpers();
1328                    self.visit_type(result_ty)?;
1329                    // A chain index is bounds-checked like any other, so it
1330                    // needs the same message in the pool.
1331                    self.note_index_check();
1332                }
1333                TypedChainPart::Call { result_ty, .. } => {
1334                    self.dependency_usage
1335                        .note_value(crate::mangle::prelude("__value_invoke_defaults"));
1336                    self.visit_type(result_ty)?;
1337                }
1338                TypedChainPart::NonNull { result_ty, .. } => {
1339                    self.visit_type(result_ty)?;
1340                    self.string_pool.intern_text(throw::NON_NULL_ASSERT_MESSAGE);
1341                }
1342                TypedChainPart::MethodCall {
1343                    iface,
1344                    name,
1345                    args,
1346                    result_ty,
1347                    span,
1348                    ..
1349                } => {
1350                    if ta.authored_call_arguments(*span).is_some()
1351                        && !crate::codegen::is_host_package(iface.as_str())
1352                    {
1353                        for helper in ["__value_member", "__value_invoke"] {
1354                            self.dependency_usage
1355                                .note_value(crate::mangle::prelude(helper));
1356                        }
1357                    }
1358                    self.extra_field_names.push(name.name.clone());
1359                    self.dependency_usage
1360                        .note_member(crate::mangle::extend(iface, &name.name));
1361                    self.visit_type(result_ty)?;
1362                    self.note_shape_dispatch(receiver_ty, args.len(), result_ty)?;
1363                }
1364            }
1365            receiver_ty = source_types.map_or_else(|| part.result_ty(), |types| &types[index + 1]);
1366        }
1367        Ok(())
1368    }
1369
1370    /// A property access on an `$ObjectShape` receiver emits every branch it
1371    /// might take: the payload-slot read (or write), a dispatch to the synthetic
1372    /// `get <prop>` / `set <prop>` method for the accessor case, and — for a
1373    /// write — the read-only-property message the getter-backed branch throws.
1374    /// The accessor branch needs its closure sig even when the program declares
1375    /// no accessor at all, since the branch is emitted on the field-name scan
1376    /// alone.
1377    ///
1378    /// The `get <prop>` / `set <prop>` `$string` globals are interned off the
1379    /// *access*, not off a declaration: whether a value is accessor-backed is a
1380    /// whole-program fact, and a library that declares an interface and reads it
1381    /// never sees the consumer's accessor implementation.
1382    fn note_shaped_property_access(&mut self, receiver_ty: &Type, prop: &str, kind: AccessorKind) {
1383        if matches!(kind, AccessorKind::Set) {
1384            self.dependency_usage.note_member(crate::mangle::extend(
1385                &crate::mangle::prelude("ObjectConstructor"),
1386                "#insertField",
1387            ));
1388        }
1389        self.mentioned_closure_sigs
1390            .push(super::field_guards::signature());
1391        if !is_shaped_receiver(receiver_ty) && !matches!(receiver_ty.peel(), Type::ClassRef { .. })
1392        {
1393            return;
1394        }
1395        self.string_pool.intern_text("Value is not callable");
1396        self.string_pool
1397            .intern_text("Unbound function has no this receiver");
1398        // Both directions need the getter's name and sig: a write scans the
1399        // `get <prop>` slot to tell a read-only property from an absent one.
1400        self.extra_field_names
1401            .push(crate::codegen::classes::accessor_getter_name(prop));
1402        self.mentioned_closure_sigs
1403            .push(crate::codegen::classes::accessor_closure_sig(
1404                AccessorKind::Get,
1405            ));
1406        if matches!(kind, AccessorKind::Set) {
1407            self.extra_field_names
1408                .push(crate::codegen::classes::accessor_setter_name(prop));
1409            self.mentioned_closure_sigs
1410                .push(crate::codegen::classes::accessor_closure_sig(
1411                    AccessorKind::Set,
1412                ));
1413            // A write also emits the read-only-property throw, for the case
1414            // where the receiver turns out to be getter-backed.
1415            self.string_pool
1416                .intern_text(throw::READ_ONLY_PROPERTY_MESSAGE);
1417        }
1418    }
1419
1420    fn note_index_check(&mut self) {
1421        self.string_pool.intern_text(bounds::INDEX_OOB_MESSAGE);
1422        for ty in [Type::Array(Box::new(Type::Unknown)), Type::Uint8Array] {
1423            self.string_pool.intern_text(&cast_check::error_prefix(&ty));
1424        }
1425        for tag in cast_check::TYPE_TAG_STRINGS {
1426            self.string_pool.intern_text(tag);
1427        }
1428    }
1429
1430    fn note_postfix_target(
1431        &mut self,
1432        ty: &Type,
1433    ) -> Result<(), crate::compiler_error::CompilerFailure> {
1434        self.dependency_usage.collect_postfix_bigint_ops(ty);
1435        self.visit_type(ty)?;
1436
1437        Ok(())
1438    }
1439}
1440
1441/// Whether a property access on this receiver lowers to the `$ObjectShape`
1442/// field-name scan, which is what carries the accessor branch. `infer_field_access`
1443/// admits a union of object shapes as well as a single one.
1444fn is_shaped_receiver(ty: &Type) -> bool {
1445    let ty = crate::typechecker::infer::narrowing::strip_nullish(ty);
1446    match ty.peel() {
1447        Type::InterfaceRef { .. } | Type::Object { .. } => true,
1448        Type::Union(members) => members
1449            .iter()
1450            .any(|m| matches!(m.peel(), Type::InterfaceRef { .. } | Type::Object { .. })),
1451        _ => false,
1452    }
1453}