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