Skip to main content

submilli_engine/
package_declaration.rs

1//! Top-level declarations of a module, indexed by symbol space.
2//!
3//! `PackageDeclaration` is to a Submilli module what a `.d.ts` file is to a TypeScript
4//! module — a description of what the module exposes to the outside world.
5//! Codegen consumes its own module's declaration as well as the declarations of
6//! every imported module to translate symbolic names into Wasm import entries
7//! and (post-MVP) to look up signatures for cross-module calls.
8
9use std::collections::{BTreeMap, BTreeSet};
10
11use crate::{Shape, Span, Type, TypedAst};
12use serde::{Deserialize, Serialize};
13
14/// The host fn a virtual package's functions dispatch through, in place of
15/// per-function Wasm imports.
16#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub enum VirtualDispatch {
18    /// `@mcp/<server>`: `submilli:mcp.call(server, tool, argsJson)`.
19    Mcp { server: String },
20    /// `@tools`, the session's application tools: `submilli:tools.call(tool, argsJson)`.
21    Tools,
22}
23
24impl VirtualDispatch {
25    /// The source-level package name.
26    pub fn package_name(&self) -> String {
27        match self {
28            VirtualDispatch::Mcp { server } => format!("@mcp/{server}"),
29            VirtualDispatch::Tools => crate::runtime::TOOLS_PACKAGE.to_string(),
30        }
31    }
32
33    /// The internal host module whose `call` every function dispatches through.
34    pub fn host_module(&self) -> &'static str {
35        match self {
36            VirtualDispatch::Mcp { .. } => crate::runtime::MCP_MODULE_NAME,
37            VirtualDispatch::Tools => crate::runtime::TOOLS_MODULE_NAME,
38        }
39    }
40}
41
42/// Maps are `BTreeMap` for deterministic iteration order; codegen derives stable function-index assignments from it.
43///
44/// Directly constructed declarations must pass [`Self::check_type_limits`] before
45/// cloning, formatting, serializing, or calling type/shape accessors. Checked
46/// compilation validates borrowed declarations before any such consumer. Mutation
47/// invalidates that validation; namespace destruction itself needs no validation.
48#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct PackageDeclaration {
50    /// Physical user-code signatures, separate from the source API. Inferred
51    /// refinements may not describe the values crossing a runtime boundary.
52    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
53    pub runtime_functions: BTreeMap<crate::MangledName, RuntimeFunction>,
54    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
55    pub runtime_globals: BTreeMap<crate::MangledName, Type>,
56    /// Also used as the Wasm import-module name when codegen imports symbols from this package.
57    pub package_name: String,
58    /// `Some` for a virtual package (`@mcp/<server>` or `@tools`). Its functions
59    /// resolve to `TypedExprKind::VirtualCall` and dispatch through one host fn
60    /// for the whole package, so codegen skips emitting per-function imports for
61    /// these. Set by the catalog mapper: the structured signal that replaces
62    /// matching on the package name.
63    #[serde(default, skip_serializing_if = "Option::is_none")]
64    pub virtual_dispatch: Option<VirtualDispatch>,
65    pub values: BTreeMap<String, ValueSymbol>,
66    /// Named declarations only (interfaces, enums, aliases). Anonymous structural shapes live on `shapes`.
67    pub types: BTreeMap<String, TypeSymbol>,
68    /// Compiler-only declarations used to reconstruct hidden runtime classes.
69    /// These names never enter source imports or rendered package declarations.
70    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
71    pub runtime_types: BTreeMap<String, TypeSymbol>,
72    /// Generic functions using the hidden runtime-descriptor argument.
73    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
74    pub runtime_generics: BTreeSet<crate::MangledName>,
75    /// Anonymous structural shapes (`Object`, `Array`, `Union`) for cross-module WasmGC subtype alignment.
76    pub shapes: Vec<crate::Shape>,
77    /// Prelude-declared namespaces (`Math`, `Temporal`). User `namespace {}` is a parse error; always empty for user-source modules.
78    pub namespaces: BTreeMap<String, NamespaceSymbol>,
79    /// Functions and static methods whose closure cache the package exports as
80    /// a global (see [`crate::mangle::closure_cache`]), so that a consumer reading
81    /// one as a value gets the same closure the package itself does.
82    #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
83    pub closure_caches: BTreeSet<crate::MangledName>,
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
87pub struct RuntimeFunction {
88    pub params: Vec<Type>,
89    pub ret: Type,
90}
91
92impl PackageDeclaration {
93    /// Prefer [`Self::with_package`] when the package name is known.
94    pub fn new() -> Self {
95        Self::with_package(crate::mangle::USER_PACKAGE)
96    }
97
98    pub fn with_package(package_name: impl Into<String>) -> Self {
99        Self {
100            package_name: package_name.into(),
101            ..Self::default()
102        }
103    }
104
105    /// The source import surface for consumers outside this package. The engine
106    /// invokes the reserved lifecycle entry point through the full declaration.
107    pub(crate) fn without_finalizer(mut self) -> Self {
108        self.values.remove("finalizer");
109        self
110    }
111
112    /// First-occurrence wins on duplicate names — typecheck has already emitted the diagnostic.
113    pub fn from_typed_ast(ast: &TypedAst) -> Self {
114        let mut defs = PackageDeclaration::with_package(ast.package_name.clone());
115        for f in &ast.functions {
116            if !f.generics.is_empty() {
117                defs.runtime_generics.insert(f.mangled_name.clone());
118            }
119            defs.values
120                .entry(f.name.name.clone())
121                .or_insert_with(|| ValueSymbol {
122                    name: f.name.name.clone(),
123                    mangled_name: f.mangled_name.clone(),
124                    declaration_span: f.name.span,
125                    kind: ValueKind::Function {
126                        generics: f.generics.clone(),
127                        params: f.params.iter().map(param_from_typed).collect(),
128                        ret: f.return_type.clone(),
129                        type_predicate: f.type_predicate.clone(),
130                        doc: f.doc.clone(),
131                    },
132                });
133        }
134        for g in &ast.globals {
135            defs.values
136                .entry(g.name.name.clone())
137                .or_insert_with(|| ValueSymbol {
138                    name: g.name.name.clone(),
139                    mangled_name: g.mangled_name.clone(),
140                    declaration_span: g.name.span,
141                    kind: match g.kind {
142                        crate::GlobalKind::Let => ValueKind::Let {
143                            ty: g.ty.clone(),
144                            doc: g.doc.clone(),
145                        },
146                        crate::GlobalKind::Const => ValueKind::Const {
147                            ty: g.ty.clone(),
148                            doc: g.doc.clone(),
149                        },
150                    },
151                });
152        }
153        // Only aliases here; interfaces and enums are consumed from ast.types directly.
154        for ty_decl in &ast.types {
155            if let crate::TypedTypeDecl::Alias(alias) = ty_decl {
156                defs.types
157                    .entry(alias.name.name.clone())
158                    .or_insert_with(|| TypeSymbol {
159                        name: alias.name.name.clone(),
160                        mangled_name: crate::mangle::package_symbol(
161                            &ast.package_name,
162                            &alias.name.name,
163                        ),
164                        declaration_span: alias.name.span,
165                        kind: TypeKind::Alias {
166                            generics: alias.generics.clone(),
167                            ty: alias.ty.clone(),
168                            doc: alias.doc.clone(),
169                        },
170                    });
171            }
172        }
173        defs.refresh_shapes();
174        defs
175    }
176
177    /// The named type declared under `mangled`. `runtime_types` holds the
178    /// declarations of every module under the name a type refers to them by;
179    /// a type re-exported from the root module is also known by its public
180    /// name, which only `types` records.
181    pub fn type_symbol(&self, mangled: &crate::MangledName) -> Option<&TypeSymbol> {
182        self.runtime_types
183            .get(mangled.as_str())
184            .or_else(|| symbol_named(self.types.values(), mangled))
185            .or_else(|| {
186                self.namespaces
187                    .values()
188                    .find_map(|namespace| namespace_type_symbol(namespace, mangled))
189            })
190    }
191
192    pub fn refresh_shapes(&mut self) {
193        self.shapes = self.collect_shapes();
194    }
195
196    pub fn collect_shapes(&self) -> Vec<Shape> {
197        let mut collector = PackageShapeCollector::default();
198        for value in self.values.values() {
199            collector.collect_value(value);
200        }
201        for ty in self.types.values() {
202            collector.collect_type_symbol(ty);
203        }
204        for namespace in self.namespaces.values() {
205            collector.collect_namespace(namespace);
206        }
207        collector.shapes
208    }
209
210    /// Rejects oversized namespace metadata and types beyond the limits in
211    /// [`crate::type_size`]. Declarations read from artifact JSON are bounded
212    /// by the parser's nesting limit, but an embedder can build one directly,
213    /// and every recursive walk over its types trusts those limits.
214    pub fn check_type_limits(&self) -> Result<(), crate::type_size::TypeTooLarge> {
215        check_namespace_limits(&self.namespaces)?;
216        let mut result = Ok(());
217        self.for_each_type(&mut |ty| {
218            if result.is_ok() {
219                result = crate::type_size::check(ty);
220            }
221        });
222        result
223    }
224
225    /// Calls `visit` on every type the declaration holds. Structures are
226    /// destructured exhaustively so a new type-bearing field has to be added
227    /// here, and nested namespaces are walked without recursion.
228    fn for_each_type(&self, visit: &mut dyn FnMut(&Type)) {
229        let Self {
230            runtime_functions,
231            runtime_globals,
232            package_name: _,
233            virtual_dispatch: _,
234            values,
235            types,
236            runtime_types,
237            runtime_generics: _,
238            shapes,
239            namespaces,
240            closure_caches: _,
241        } = self;
242        for RuntimeFunction { params, ret } in runtime_functions.values() {
243            params.iter().for_each(&mut *visit);
244            visit(ret);
245        }
246        runtime_globals.values().for_each(&mut *visit);
247        values.values().for_each(|value| value_types(value, visit));
248        types
249            .values()
250            .chain(runtime_types.values())
251            .for_each(|symbol| type_symbol_types(symbol, visit));
252        for shape in shapes {
253            match shape {
254                Shape::Object { fields, index } => {
255                    fields.values().for_each(|field| visit(&field.ty));
256                    if let Some(index) = index {
257                        visit(&index.value);
258                    }
259                }
260                Shape::Array(element) => visit(element),
261                Shape::Tuple(members) => {
262                    members.iter().for_each(&mut *visit);
263                }
264                Shape::Union(members) => {
265                    members.iter().for_each(&mut *visit);
266                }
267            }
268        }
269        let mut pending: Vec<&NamespaceSymbol> = namespaces.values().collect();
270        while let Some(namespace) = pending.pop() {
271            let NamespaceSymbol {
272                name: _,
273                mangled_prefix: _,
274                declaration_span: _,
275                values,
276                types,
277                namespaces,
278                doc: _,
279            } = namespace;
280            values.values().for_each(|value| value_types(value, visit));
281            types
282                .values()
283                .for_each(|symbol| type_symbol_types(symbol, visit));
284            pending.extend(namespaces.values());
285        }
286    }
287}
288
289fn check_namespace_limits(
290    namespaces: &BTreeMap<String, NamespaceSymbol>,
291) -> Result<(), crate::type_size::TypeTooLarge> {
292    use crate::compiler_limits::{
293        MAX_NAMESPACE_DEPTH, MAX_NAMESPACE_NODES, MAX_NAMESPACE_PATH_BYTES,
294    };
295    use crate::type_size::TypeTooLarge;
296
297    // Iterator frames bound the frontier by depth, not the width of public input.
298    let mut pending = vec![(namespaces.iter(), 0usize)];
299    let mut nodes = 0;
300    let mut path_bytes = 0usize;
301    while let Some((siblings, prefix_bytes)) = pending.last_mut() {
302        let Some((name, namespace)) = siblings.next() else {
303            pending.pop();
304            continue;
305        };
306        let path_len = prefix_bytes.saturating_add(name.len());
307        nodes += 1;
308        path_bytes = path_bytes.saturating_add(path_len);
309        for name in namespace.types.keys() {
310            path_bytes =
311                path_bytes.saturating_add(path_len.saturating_add(1).saturating_add(name.len()));
312            if path_bytes > MAX_NAMESPACE_PATH_BYTES {
313                return Err(TypeTooLarge::NamespaceMetadata);
314            }
315        }
316        if nodes > MAX_NAMESPACE_NODES
317            || pending.len() > MAX_NAMESPACE_DEPTH
318            || path_bytes > MAX_NAMESPACE_PATH_BYTES
319        {
320            return Err(TypeTooLarge::NamespaceMetadata);
321        }
322        if !namespace.namespaces.is_empty() {
323            pending.push((namespace.namespaces.iter(), path_len.saturating_add(1)));
324        }
325    }
326    Ok(())
327}
328
329fn namespace_type_symbol<'a>(
330    namespace: &'a NamespaceSymbol,
331    mangled: &crate::MangledName,
332) -> Option<&'a TypeSymbol> {
333    symbol_named(namespace.types.values(), mangled).or_else(|| {
334        namespace
335            .namespaces
336            .values()
337            .find_map(|child| namespace_type_symbol(child, mangled))
338    })
339}
340
341fn symbol_named<'a>(
342    mut symbols: impl Iterator<Item = &'a TypeSymbol>,
343    mangled: &crate::MangledName,
344) -> Option<&'a TypeSymbol> {
345    symbols.find(|symbol| symbol.mangled_name == *mangled)
346}
347
348fn value_types(value: &ValueSymbol, visit: &mut dyn FnMut(&Type)) {
349    match &value.kind {
350        ValueKind::Function {
351            generics,
352            params,
353            ret,
354            type_predicate,
355            doc: _,
356        } => {
357            generics.constraints.values().for_each(&mut *visit);
358            params.iter().for_each(|param| visit(&param.ty));
359            visit(ret);
360            if let Some(predicate) = type_predicate {
361                visit(&predicate.asserted_type);
362            }
363        }
364        ValueKind::Let { ty, doc: _ } | ValueKind::Const { ty, doc: _ } => visit(ty),
365    }
366}
367
368fn type_symbol_types(symbol: &TypeSymbol, visit: &mut dyn FnMut(&Type)) {
369    match &symbol.kind {
370        TypeKind::Interface { generics, .. }
371        | TypeKind::Class { generics, .. }
372        | TypeKind::Alias { generics, .. } => generics.constraints.values().for_each(&mut *visit),
373        _ => {}
374    }
375    match &symbol.kind {
376        TypeKind::Interface {
377            generics: _,
378            methods,
379            properties,
380            index,
381            dispatch: _,
382            doc: _,
383        } => {
384            methods
385                .values()
386                .for_each(|method| method_types(method, visit));
387            properties.values().for_each(|property| visit(&property.ty));
388            if let Some(index) = index {
389                visit(&index.value);
390            }
391        }
392        TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
393        TypeKind::Alias {
394            generics: _,
395            ty,
396            doc: _,
397        } => visit(ty),
398        TypeKind::Class {
399            generics: _,
400            fields,
401            narrowing_checks,
402            methods,
403            method_visibility: _,
404            accessors,
405            constructor,
406            constructor_visibility: _,
407            statics,
408            static_visibility: _,
409            static_fields,
410            extends,
411            implements: _,
412            doc: _,
413        } => {
414            fields
415                .values()
416                .chain(static_fields.values())
417                .for_each(|field| visit(&field.ty));
418            for check in narrowing_checks.values() {
419                narrowing_check_types(check, visit);
420            }
421            methods
422                .values()
423                .chain(statics.values())
424                .for_each(|method| method_types(method, visit));
425            for accessor in accessors {
426                match accessor {
427                    AccessorSig::Getter { name: _, ret_ty } => visit(ret_ty),
428                    AccessorSig::Setter { name: _, param } => visit(&param.ty),
429                }
430            }
431            constructor.iter().for_each(|param| visit(&param.ty));
432            if let Some(extends) = extends {
433                extends.args.iter().for_each(&mut *visit);
434            }
435        }
436    }
437}
438
439fn narrowing_check_types(check: &crate::FieldNarrowingCheck, visit: &mut dyn FnMut(&Type)) {
440    let crate::FieldNarrowingCheck {
441        declaration: _,
442        test,
443        minimal_test_target,
444        message: _,
445    } = check;
446    if let Some(target) = minimal_test_target {
447        visit(target);
448    }
449    match test {
450        crate::FieldNarrowingTest::Shape(ty) => visit(ty),
451        crate::FieldNarrowingTest::Interface(interface) => {
452            let crate::InterfaceNarrowingTest {
453                index,
454                members,
455                methods: _,
456                non_shape_carriers,
457                shape_allowed: _,
458                nullable: _,
459                undefined: _,
460            } = interface;
461            if let Some(index) = index {
462                visit(&index.value);
463            }
464            members.values().for_each(|member| visit(&member.ty));
465            for carrier in non_shape_carriers {
466                match carrier {
467                    crate::InterfaceCarrier::Array(element)
468                    | crate::InterfaceCarrier::Set(element) => visit(element),
469                    crate::InterfaceCarrier::Map(key, value) => {
470                        visit(key);
471                        visit(value);
472                    }
473
474                    crate::InterfaceCarrier::Number
475                    | crate::InterfaceCarrier::Boolean
476                    | crate::InterfaceCarrier::String
477                    | crate::InterfaceCarrier::BigInt
478                    | crate::InterfaceCarrier::Uint8Array
479                    | crate::InterfaceCarrier::ArrayAny
480                    | crate::InterfaceCarrier::MapAny
481                    | crate::InterfaceCarrier::SetAny
482                    | crate::InterfaceCarrier::RegExp
483                    | crate::InterfaceCarrier::RegExpMatch
484                    | crate::InterfaceCarrier::RegExpMatchArray
485                    | crate::InterfaceCarrier::TemporalInstant
486                    | crate::InterfaceCarrier::TemporalDuration
487                    | crate::InterfaceCarrier::TemporalZonedDateTime
488                    | crate::InterfaceCarrier::TemporalPlainDate
489                    | crate::InterfaceCarrier::TemporalPlainTime
490                    | crate::InterfaceCarrier::TemporalPlainDateTime
491                    | crate::InterfaceCarrier::TemporalPlainYearMonth
492                    | crate::InterfaceCarrier::TemporalPlainMonthDay
493                    | crate::InterfaceCarrier::ObjectShape
494                    | crate::InterfaceCarrier::Url
495                    | crate::InterfaceCarrier::FsStat
496                    | crate::InterfaceCarrier::FsPeek
497                    | crate::InterfaceCarrier::FsDirEntry
498                    | crate::InterfaceCarrier::FsInfo
499                    | crate::InterfaceCarrier::FsMountInfo
500                    | crate::InterfaceCarrier::FsFileWriter
501                    | crate::InterfaceCarrier::HttpResponse
502                    | crate::InterfaceCarrier::HttpDownloadResult
503                    | crate::InterfaceCarrier::SessionEntry
504                    | crate::InterfaceCarrier::SessionPage => {}
505                }
506            }
507        }
508        crate::FieldNarrowingTest::NonNull
509        | crate::FieldNarrowingTest::Substituted
510        | crate::FieldNarrowingTest::Representation => {}
511    }
512}
513
514fn method_types(method: &MethodSig, visit: &mut dyn FnMut(&Type)) {
515    method.generics.constraints.values().for_each(&mut *visit);
516    let MethodSig {
517        optional: _,
518        generics: _,
519        params,
520        ret,
521        predicate,
522        doc: _,
523    } = method;
524    params.iter().for_each(|param| visit(&param.ty));
525    visit(ret);
526    if let Some(predicate) = predicate {
527        visit(&predicate.asserted_type);
528    }
529}
530
531#[derive(Default)]
532struct PackageShapeCollector {
533    shapes: Vec<Shape>,
534    seen: BTreeSet<Shape>,
535}
536
537impl PackageShapeCollector {
538    fn collect_value(&mut self, value: &ValueSymbol) {
539        match &value.kind {
540            ValueKind::Function {
541                params,
542                ret,
543                type_predicate,
544                ..
545            } => {
546                for param in params {
547                    self.collect_type(&param.ty);
548                }
549                self.collect_type(ret);
550                if let Some(predicate) = type_predicate {
551                    self.collect_type(&predicate.asserted_type);
552                }
553            }
554            ValueKind::Let { ty, .. } | ValueKind::Const { ty, .. } => self.collect_type(ty),
555        }
556    }
557
558    fn collect_type_symbol(&mut self, ty: &TypeSymbol) {
559        match &ty.kind {
560            TypeKind::Interface {
561                methods,
562                properties,
563                index,
564                ..
565            } => {
566                if let Some(index) = index {
567                    self.collect_type(&index.value);
568                }
569                for method in methods.values() {
570                    for param in &method.params {
571                        self.collect_type(&param.ty);
572                    }
573                    self.collect_type(&method.ret);
574                    if let Some(predicate) = &method.predicate {
575                        self.collect_type(&predicate.asserted_type);
576                    }
577                }
578                for property in properties.values() {
579                    self.collect_type(&property.ty);
580                }
581            }
582            TypeKind::Class {
583                fields,
584                methods,
585                accessors,
586                constructor,
587                ..
588            } => {
589                for field in fields.values() {
590                    self.collect_type(&field.ty);
591                }
592                for method in methods.values() {
593                    for param in &method.params {
594                        self.collect_type(&param.ty);
595                    }
596                    self.collect_type(&method.ret);
597                }
598                for acc in accessors {
599                    match acc {
600                        AccessorSig::Getter { ret_ty, .. } => self.collect_type(ret_ty),
601                        AccessorSig::Setter { param, .. } => self.collect_type(&param.ty),
602                    }
603                }
604                for param in constructor {
605                    self.collect_type(&param.ty);
606                }
607            }
608            TypeKind::Alias { ty, .. } => self.collect_type(ty),
609            TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
610        }
611    }
612
613    fn collect_namespace(&mut self, namespace: &NamespaceSymbol) {
614        for value in namespace.values.values() {
615            self.collect_value(value);
616        }
617        for ty in namespace.types.values() {
618            self.collect_type_symbol(ty);
619        }
620        for child in namespace.namespaces.values() {
621            self.collect_namespace(child);
622        }
623    }
624
625    fn collect_type(&mut self, ty: &Type) {
626        match ty {
627            Type::TemplateLiteral { substitutions, .. } => {
628                for inner in substitutions {
629                    self.collect_type(inner);
630                }
631            }
632            Type::Object { fields, .. } => {
633                self.collect_shape(ty);
634                for field in fields.values() {
635                    self.collect_type(&field.ty);
636                }
637            }
638            Type::Array(elem) => {
639                self.collect_shape(ty);
640                self.collect_type(elem);
641            }
642            Type::Tuple(elements) => {
643                self.collect_shape(ty);
644                for elem in elements {
645                    self.collect_type(elem);
646                }
647            }
648            Type::Union(members) => {
649                self.collect_shape(ty);
650                for member in members {
651                    self.collect_type(member);
652                }
653            }
654            Type::Function { params, ret, .. } => {
655                for param in params {
656                    self.collect_type(param);
657                }
658                self.collect_type(ret);
659            }
660            Type::InterfaceRef { args, .. }
661            | Type::ClassRef { args, .. }
662            | Type::AliasRef { args, .. } => {
663                for arg in args {
664                    self.collect_type(arg);
665                }
666            }
667            Type::Alias { ty, .. }
668            | Type::Refined { ty, .. }
669            | Type::Readonly(ty)
670            | Type::KeyOf(ty) => {
671                self.collect_type(ty);
672            }
673            Type::IndexedAccess { object, index } => {
674                self.collect_type(object);
675                self.collect_type(index);
676            }
677            Type::Number
678            | Type::BigInt
679            | Type::BigIntLiteral(_)
680            | Type::NumberLiteral(_)
681            | Type::String
682            | Type::StringLiteral(_)
683            | Type::Uint8Array
684            | Type::Boolean
685            | Type::BooleanLiteral(_)
686            | Type::Null
687            | Type::Undefined
688            | Type::Void
689            | Type::Never
690            | Type::Unknown
691            | Type::NonPrimitive
692            | Type::Error
693            | Type::TypeVar(_)
694            | Type::GenericParam { .. }
695            | Type::NumberEnum { .. }
696            | Type::StringEnum { .. } => {}
697        }
698    }
699
700    fn collect_shape(&mut self, ty: &Type) {
701        let Some(shape) = Shape::from_type(ty) else {
702            return;
703        };
704        if self.seen.insert(shape.clone()) {
705            self.shapes.push(shape);
706        }
707    }
708}
709
710#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
711pub struct ValueSymbol {
712    /// The symbol's own (unaliased) name within its package.
713    pub name: String,
714    /// The key codegen uses for cross-module symbol lookup.
715    pub mangled_name: crate::MangledName,
716    pub declaration_span: Span,
717    pub kind: ValueKind,
718}
719
720#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
721pub struct Param {
722    pub name: String,
723    pub ty: Type,
724    pub default: Option<DefaultValue>,
725    /// Whether a caller may omit the argument. Signatures the typechecker
726    /// builds from source set it for `?` and for defaulted parameters alike,
727    /// but a host declaration may give only `default`, so readers ask
728    /// [`Param::is_omittable`], which covers both.
729    #[serde(default)]
730    pub optional: bool,
731    /// Callee sees `T[]`; call-site collects trailing args into a fresh array.
732    pub rest: bool,
733}
734
735impl Param {
736    /// Whether a caller may leave the argument out: declared `?`, or defaulted.
737    pub fn is_omittable(&self) -> bool {
738        self.optional || self.default.is_some()
739    }
740}
741
742/// Optional fixed parameters at the end of a signature, excluding its rest slot.
743pub(crate) fn optional_parameter_count(params: &[Param]) -> usize {
744    params
745        .iter()
746        .rev()
747        .skip_while(|param| param.rest)
748        .take_while(|param| param.is_omittable())
749        .count()
750}
751
752impl Param {
753    pub fn new(name: impl Into<String>, ty: Type) -> Self {
754        Self {
755            name: name.into(),
756            ty,
757            default: None,
758            optional: false,
759            rest: false,
760        }
761    }
762
763    pub fn with_default(name: impl Into<String>, ty: Type, default: DefaultValue) -> Self {
764        Self {
765            name: name.into(),
766            ty,
767            default: Some(default),
768            optional: true,
769            rest: false,
770        }
771    }
772
773    /// The caller may omit this parameter, supplying `undefined`.
774    pub fn optional(name: impl Into<String>, ty: Type) -> Self {
775        Self {
776            optional: true,
777            ..Self::new(name, ty)
778        }
779    }
780
781    /// `ty` is the array type `T[]`. Must be last; cannot have a default.
782    pub fn rest(name: impl Into<String>, ty: Type) -> Self {
783        Self {
784            name: name.into(),
785            ty,
786            default: None,
787            optional: false,
788            rest: true,
789        }
790    }
791
792    /// Empty name signals "positional" to the diagnostic lifter.
793    pub fn anon(ty: Type) -> Self {
794        Self {
795            name: String::new(),
796            ty,
797            default: None,
798            optional: false,
799            rest: false,
800        }
801    }
802}
803
804/// Lower a body-pass `TypedParam` to an exported `Param`, preserving `rest` and
805/// the resolved default so cross-module callers can omit the argument.
806pub(crate) fn param_from_typed(p: &crate::TypedParam) -> Param {
807    Param {
808        name: p.name.name.clone(),
809        ty: p.ty.clone(),
810        default: p.default.clone(),
811        optional: p.optional,
812        rest: p.rest,
813    }
814}
815
816/// Restricted to shapes materializable without re-evaluating arbitrary source.
817/// Re-evaluated per call (matching JS/TS) — a fresh `TypedExpr` is spliced at each call site.
818#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
819pub enum DefaultValue {
820    Undefined,
821    Number(#[serde(with = "crate::artifact_f64")] f64),
822    /// A number for an omitted argument that an explicit `undefined` does not
823    /// share, as in JavaScript: `splice(1)` removes to the end, while
824    /// `splice(1, undefined)` converts `undefined` to `0` and removes nothing.
825    OmittedNumber {
826        #[serde(with = "crate::artifact_f64")]
827        omitted: f64,
828        #[serde(with = "crate::artifact_f64")]
829        undefined: f64,
830    },
831    /// A string for an omitted argument only: an explicit `undefined` is passed
832    /// as it is, as JavaScript's `String()` is `""` while `String(undefined)`
833    /// is `"undefined"`.
834    OmittedString(String),
835    String(String),
836    Boolean(bool),
837    Null,
838    /// `[]` — the parameter's resolved type must be `Type::Array(_)`;
839    /// signature-time validation checks this.
840    EmptyArray,
841    /// `{}` — the parameter's resolved type must be `Type::Object { .. }`. Synthesizes an
842    /// empty object literal; absent optional fields serialize away. Used for MCP tools
843    /// whose input schema marks nothing required, so a zero-arg call type-checks.
844    EmptyObject,
845    /// Codegen emits a `GlobalGet` at each fill-in site.
846    GlobalConst(crate::MangledName),
847    /// Fields mirror `TypedExprKind::{Number,String}EnumMember` so the call-site synthesizer can build that node verbatim.
848    EnumVariant {
849        enum_mangled: crate::MangledName,
850        variant: String,
851        value: EnumVariantValue,
852    },
853}
854
855impl DefaultValue {
856    /// Whether an explicit `undefined` argument is replaced too: by the default,
857    /// as for a JavaScript default parameter, or by `OmittedNumber`'s
858    /// `undefined` value.
859    pub fn replaces_undefined(&self) -> bool {
860        !matches!(self, Self::OmittedString(_))
861    }
862}
863
864#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
865pub enum EnumVariantValue {
866    Number(#[serde(with = "crate::artifact_f64")] f64),
867    String(String),
868}
869
870#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
871pub enum ValueKind {
872    Function {
873        /// Names appear inside `params`/`ret` as `Type::Var(name)`, resolved per call site by `TypeParamSubstitution`.
874        generics: GenericParameters,
875        params: Vec<Param>,
876        ret: Type,
877        /// Type-guard (`x is T`); inferer copies this into `Type::Function` at each reference site.
878        type_predicate: Option<crate::TypePredicate>,
879        doc: Option<crate::DocComment>,
880    },
881    Let {
882        /// Resolved type of the binding. `Type::Error` when the annotation
883        /// was missing or unresolvable — the originating diagnostic is in
884        /// the diagnostics list.
885        ty: Type,
886        doc: Option<crate::DocComment>,
887    },
888    Const {
889        ty: Type,
890        doc: Option<crate::DocComment>,
891    },
892}
893
894/// Ordered type parameter names and their optional upper bounds. Keeping the
895/// bounds beside the names preserves unused parameters in exported signatures.
896#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
897pub struct GenericParameters {
898    pub names: Vec<String>,
899    pub constraints: Box<BTreeMap<String, Type>>,
900}
901
902impl std::ops::Deref for GenericParameters {
903    type Target = [String];
904
905    fn deref(&self) -> &[String] {
906        &self.names
907    }
908}
909
910impl GenericParameters {
911    /// Construct parameters known to have no bounds, such as built-in
912    /// declarations. Parsed declarations must resolve and retain their bounds.
913    pub fn unconstrained(names: Vec<String>) -> Self {
914        Self {
915            names,
916            constraints: Box::default(),
917        }
918    }
919    pub(crate) fn select_names(&self, names: &[String]) -> Self {
920        Self {
921            names: names.to_vec(),
922            constraints: Box::new(
923                names
924                    .iter()
925                    .filter_map(|name| {
926                        self.constraints
927                            .get(name)
928                            .map(|bound| (name.clone(), bound.clone()))
929                    })
930                    .collect(),
931            ),
932        }
933    }
934}
935
936impl<'a> IntoIterator for &'a GenericParameters {
937    type Item = &'a String;
938    type IntoIter = std::slice::Iter<'a, String>;
939
940    fn into_iter(self) -> Self::IntoIter {
941        self.names.iter()
942    }
943}
944
945#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
946pub struct TypeSymbol {
947    pub name: String,
948    /// Same role as `ValueSymbol::mangled_name`, for the type namespace.
949    pub mangled_name: crate::MangledName,
950    pub declaration_span: Span,
951    pub kind: TypeKind,
952}
953
954// `Interface` carries an optional `DocComment` which makes
955// the enum non-trivial in size; the enum is rarely instantiated in
956// hot paths so the variance is acceptable.
957#[allow(clippy::large_enum_variant)]
958#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
959pub enum TypeKind {
960    /// Single declaration site per name — no reopening, no `extends` in v1.
961    /// Codegen has no Wasm representation for the interface itself; it only drives method dispatch.
962    Interface {
963        generics: GenericParameters,
964        methods: BTreeMap<String, MethodSig>,
965        /// Read via `expr.name` (not `expr.name()`). Writable unless the
966        /// declaration carries a `readonly` modifier (`PropertySig.readonly`).
967        properties: BTreeMap<String, PropertySig>,
968        index: Option<crate::IndexSignature>,
969        dispatch: Dispatch,
970        doc: Option<crate::DocComment>,
971    },
972    /// Auto-numbered at bind time (source AST stores `None`; bound form holds the resolved value).
973    NumberEnum {
974        #[serde(with = "crate::artifact_f64::pairs")]
975        variants: Vec<(String, f64)>,
976        doc: Option<crate::DocComment>,
977    },
978    /// Every variant must have an explicit initializer; auto-numbering doesn't apply.
979    StringEnum {
980        variants: Vec<(String, String)>,
981        doc: Option<crate::DocComment>,
982    },
983    /// Stored `ty` is the fully resolved alias body. At use sites the inferer wraps the result in
984    /// `Type::Alias { name, args, ty }` so the alias label flows through `Display`.
985    Alias {
986        generics: GenericParameters,
987        ty: Type,
988        doc: Option<crate::DocComment>,
989    },
990    /// A `class` declaration. Nominal (`Type::ClassRef` keys on the symbol's mangled name).
991    /// Single inheritance via `extends`; `implements` is structural conformance only.
992    /// Privacy is enforced at the access site (module-scoped), not by stripping here.
993    /// Methods are always vtable-dispatched (overrides slot into the inherited index),
994    /// so there is no per-class `dispatch` — unlike `Interface`, where the prelude opts
995    /// into direct/static dispatch.
996    Class {
997        /// Class-level type parameters, erased at codegen (every `T`-typed slot
998        /// is a boxed value slot). Substituted at use sites from the
999        /// `ClassRef`'s args.
1000        generics: GenericParameters,
1001        fields: BTreeMap<String, FieldSig>,
1002        /// Runtime read guards for fields whose declaration narrows an inherited
1003        /// slot. Carried across packages because the consumer reconstructs the
1004        /// class layout and emits its reads independently.
1005        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1006        narrowing_checks: BTreeMap<String, crate::FieldNarrowingCheck>,
1007        methods: BTreeMap<String, MethodSig>,
1008        /// Per-method visibility, keyed alongside `methods` (kept separate so
1009        /// `MethodSig` stays shared with interfaces, which have no visibility).
1010        method_visibility: BTreeMap<String, crate::Visibility>,
1011        /// Accessor functions (`get`/`set`) — one entry each, mirroring the typed
1012        /// AST. A property's read type (getter) and write type (setter) are
1013        /// independent; the property itself is also in `fields` (its read type) for
1014        /// the type system. Carries the setter's parameter name + type so the
1015        /// public surface (docs, `.d.ts`) renders the full signature.
1016        accessors: Vec<AccessorSig>,
1017        /// Constructor parameter signature (no return type).
1018        constructor: Vec<Param>,
1019        /// A `private` constructor may be called, and the class extended, only in
1020        /// the module that declares the class.
1021        #[serde(default, skip_serializing_if = "crate::Visibility::is_public")]
1022        constructor_visibility: crate::Visibility,
1023        /// Static methods — self-less functions dispatched by name on the class
1024        /// object (`Class#static#name`), never through the vtable. Inherited down
1025        /// the `extends` chain by name resolution at the use site.
1026        #[serde(default)]
1027        statics: BTreeMap<String, MethodSig>,
1028        /// Per-static-method visibility, mirroring `method_visibility`.
1029        #[serde(default)]
1030        static_visibility: BTreeMap<String, crate::Visibility>,
1031        /// Static fields, backed by module globals (`Class#static#name`). `readonly`
1032        /// is declaration-driven; a mutable one is writable through the class name.
1033        #[serde(default)]
1034        static_fields: BTreeMap<String, FieldSig>,
1035        /// Resolved parent class, if any.
1036        extends: Option<ClassExtends>,
1037        /// Resolved interfaces this class declares it implements.
1038        implements: Vec<crate::MangledName>,
1039        doc: Option<crate::DocComment>,
1040    },
1041    // `String` is absent — it's an intrinsic declared per-consumer via `declare_intrinsic_types`.
1042}
1043
1044/// A class's resolved `extends` clause. Parent and type args are one fact:
1045/// every chain walker that hops to the parent must zip `args` against the
1046/// parent's generics, so they travel together.
1047#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1048pub struct ClassExtends {
1049    pub parent: crate::MangledName,
1050    /// Type args applied to the parent's generics; may mention the child's own
1051    /// generics. Empty for a non-generic parent.
1052    pub args: Vec<Type>,
1053}
1054
1055impl ClassExtends {
1056    /// Extends a non-generic parent.
1057    pub fn plain(parent: crate::MangledName) -> Self {
1058        Self {
1059            parent,
1060            args: Vec::new(),
1061        }
1062    }
1063}
1064
1065#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
1066pub enum Dispatch {
1067    /// Codegen pushes the receiver as the first argument before calling the prelude wrapper.
1068    Direct,
1069    /// Codegen evaluates the receiver (for side effects) and drops it, then calls with only user args.
1070    Static,
1071    /// `call_ref` via a funcref loaded from the receiver's vtable slot.
1072    VTable,
1073}
1074
1075#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1076pub struct MethodSig {
1077    /// Declared `m?(…)`: the member may be absent, so a call goes through `?.`.
1078    #[serde(default)]
1079    pub optional: bool,
1080    pub generics: GenericParameters,
1081    pub params: Vec<Param>,
1082    pub ret: Type,
1083    /// Type-guard predicate (`x is T`); `None` for ordinary methods.
1084    pub predicate: Option<crate::TypePredicate>,
1085    pub doc: Option<crate::DocComment>,
1086}
1087
1088/// One accessor function on a class property. Getter and setter are independent
1089/// (TS 4.3+): a property may declare a getter, a setter, or both, and their types
1090/// need not match. The setter carries its parameter (name + write type).
1091#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1092pub enum AccessorSig {
1093    Getter { name: String, ret_ty: Type },
1094    Setter { name: String, param: Param },
1095}
1096
1097impl AccessorSig {
1098    pub fn name(&self) -> &str {
1099        match self {
1100            AccessorSig::Getter { name, .. } | AccessorSig::Setter { name, .. } => name,
1101        }
1102    }
1103}
1104
1105/// `readonly` mirrors the declared modifier and forbids writes through the
1106/// interface (shallow — it does not affect assignability). Optional properties
1107/// widen reads to `T | undefined`.
1108///
1109/// `intrinsic` marks a member codegen emits inline as an instruction sequence
1110/// (`String#length`/`Array#length` → payload `struct.get` + `array.len`): no
1111/// getter exists to import, and the emitter owns its lowering.
1112#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1113pub struct PropertySig {
1114    pub ty: Type,
1115    pub readonly: bool,
1116    pub optional: bool,
1117    #[serde(default)]
1118    pub intrinsic: bool,
1119    pub doc: Option<crate::DocComment>,
1120}
1121
1122/// A class instance field. Unlike [`PropertySig`], a field may be mutable and carries
1123/// `visibility`; `readonly` fields are writeable only inside the declaring constructor.
1124/// Optional fields widen reads to `T | undefined`.
1125#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1126pub struct FieldSig {
1127    pub ty: Type,
1128    pub visibility: crate::Visibility,
1129    pub readonly: bool,
1130    pub optional: bool,
1131    /// Declared as a bodyless optional method (`f?(): T;`), which a subclass
1132    /// may override with a method.
1133    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1134    pub method: bool,
1135    pub doc: Option<crate::DocComment>,
1136}
1137
1138/// No runtime representation; member resolution is fully static.
1139/// Recursive derived operations require the containing declaration's successful
1140/// [`PackageDeclaration::check_type_limits`] check since its last mutation.
1141/// Namespaced types are also mirrored into `PackageDeclaration::types` under their full dotted key.
1142#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1143pub struct NamespaceSymbol {
1144    pub name: String,
1145    /// Shared by every export of this namespace; children extend it via `mangle::extend`.
1146    pub mangled_prefix: crate::MangledName,
1147    /// Synthetic span for prelude namespaces.
1148    pub declaration_span: Span,
1149    pub values: BTreeMap<String, ValueSymbol>,
1150    /// Also mirrored into `PackageDeclaration::types` under the full dotted key for `lookup_named_type`.
1151    pub types: BTreeMap<String, TypeSymbol>,
1152    pub namespaces: BTreeMap<String, NamespaceSymbol>,
1153    pub doc: Option<crate::DocComment>,
1154}
1155
1156impl Drop for NamespaceSymbol {
1157    fn drop(&mut self) {
1158        drop_namespace_children(&mut self.namespaces);
1159    }
1160}
1161
1162fn drop_namespace_children(namespaces: &mut BTreeMap<String, NamespaceSymbol>) {
1163    if namespaces.is_empty() {
1164        return;
1165    }
1166    let mut pending = Vec::new();
1167    if pending.try_reserve(1).is_err() {
1168        drop_namespace_children_without_allocation(namespaces);
1169        return;
1170    }
1171    pending.push(std::mem::take(namespaces).into_values());
1172    while let Some(siblings) = pending.last_mut() {
1173        let Some(mut child) = siblings.next() else {
1174            pending.pop();
1175            continue;
1176        };
1177        if child.namespaces.is_empty() {
1178            continue;
1179        }
1180        if pending.try_reserve(1).is_err() {
1181            drop_namespace_children_without_allocation(&mut child.namespaces);
1182        } else {
1183            pending.push(std::mem::take(&mut child.namespaces).into_values());
1184        }
1185        // Each child now owns no namespaces, so its Drop cannot recurse.
1186    }
1187}
1188
1189fn drop_namespace_children_without_allocation(namespaces: &mut BTreeMap<String, NamespaceSymbol>) {
1190    // Under memory pressure, repeatedly find and remove a leaf. This is slower
1191    // than the iterator stack but needs neither allocation nor native recursion.
1192    while !namespaces.is_empty() {
1193        let mut branch = &mut *namespaces;
1194        loop {
1195            if branch
1196                .first_key_value()
1197                .is_some_and(|(_, child)| child.namespaces.is_empty())
1198            {
1199                branch.pop_first();
1200                break;
1201            }
1202            // The root is nonempty; descent happens only into a nonempty child.
1203            // No callbacks or mutation occur between the check and this access.
1204            branch = &mut branch
1205                .first_entry()
1206                .expect("nonempty namespace branch")
1207                .into_mut()
1208                .namespaces;
1209        }
1210    }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215    use super::PackageDeclaration;
1216    use crate::{Asi, Token, TokenKind, Type, capture, check, desugar, infer, parse};
1217
1218    fn typed_ast(source: &str) -> crate::TypedAst {
1219        let mut asi = Asi::new(source, crate::FileId(0));
1220        let mut tokens: Vec<Token> = Vec::new();
1221        loop {
1222            let tok = asi.next_token();
1223            let is_eof = matches!(tok.kind, TokenKind::Eof);
1224            tokens.push(tok);
1225            if is_eof {
1226                break;
1227            }
1228        }
1229        let _ = asi.into_diagnostics();
1230        let (ast, _) = parse(source, tokens, crate::FileId(0));
1231        let (prelude_defs, host_defs, _) =
1232            crate::runtime::prelude::cached_runtime_package_declarations();
1233        let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
1234        packages.extend(prelude_defs.iter());
1235        packages.extend(host_defs.iter());
1236        let (mut ta, _) = infer(source, "main", &ast, &packages);
1237        let _ = check(&ta);
1238        ta = capture(ta).unwrap();
1239        ta = desugar(ta, crate::FileId(0)).unwrap();
1240        ta
1241    }
1242
1243    #[test]
1244    fn collects_object_shape_from_let_annotation() {
1245        let ta = typed_ast("let p: { x: number; y: number } = { x: 1, y: 2 };");
1246        let defs = PackageDeclaration::from_typed_ast(&ta);
1247        let object = defs
1248            .shapes
1249            .iter()
1250            .find_map(|s| match s {
1251                crate::Shape::Object { fields, .. } => Some(fields),
1252                _ => None,
1253            })
1254            .expect("object shape in defs.shapes");
1255        assert_eq!(
1256            object.get("x"),
1257            Some(&crate::ObjectField::required(Type::Number)),
1258        );
1259        assert_eq!(
1260            object.get("y"),
1261            Some(&crate::ObjectField::required(Type::Number)),
1262        );
1263    }
1264
1265    #[test]
1266    fn generic_constraints_survive_serialization_including_unused_parameters() {
1267        let (ta, diagnostics) = crate::typecheck_to_typed_ast(
1268            "type Empty<T extends number> = {};\nfunction identity<T extends number, U extends T>(value: U): U { return value; }\nfunction main(): void {}",
1269            crate::FileId(0),
1270        );
1271        assert!(
1272            diagnostics
1273                .iter()
1274                .all(|d| d.severity != crate::Severity::Error),
1275            "{diagnostics:?}"
1276        );
1277        let declaration = PackageDeclaration::from_typed_ast(&ta);
1278        let serialized = serde_json::to_string(&declaration).unwrap();
1279        let restored: PackageDeclaration = serde_json::from_str(&serialized).unwrap();
1280        let crate::ValueKind::Function { generics, .. } = &restored.values["identity"].kind else {
1281            panic!("function declaration");
1282        };
1283        assert_eq!(generics.names, ["T", "U"]);
1284        assert_eq!(generics.constraints.get("T"), Some(&Type::Number));
1285        assert_eq!(
1286            generics.constraints.get("U"),
1287            Some(&Type::TypeVar("T".into()))
1288        );
1289        let crate::TypeKind::Alias { generics, .. } = &restored.types["Empty"].kind else {
1290            panic!("alias declaration");
1291        };
1292        assert_eq!(generics.constraints.get("T"), Some(&Type::Number));
1293        restored.check_type_limits().unwrap();
1294    }
1295
1296    #[test]
1297    fn preserves_readonly_and_writable_object_shapes() {
1298        let ta =
1299            typed_ast("let a: { readonly x: number } = { x: 1 }; let b: { x: number } = { x: 2 };");
1300        let defs = PackageDeclaration::from_typed_ast(&ta);
1301        let readonly_flags: std::collections::BTreeSet<bool> = defs
1302            .shapes
1303            .iter()
1304            .filter_map(|shape| {
1305                let crate::Shape::Object { fields, .. } = shape else {
1306                    return None;
1307                };
1308                fields.get("x").map(|field| field.readonly)
1309            })
1310            .collect();
1311        assert_eq!(
1312            readonly_flags,
1313            std::collections::BTreeSet::from([false, true])
1314        );
1315    }
1316
1317    #[test]
1318    fn collects_array_shape_from_let_annotation() {
1319        let ta = typed_ast("let xs: number[] = [1, 2, 3];");
1320        let defs = PackageDeclaration::from_typed_ast(&ta);
1321        assert!(
1322            defs.shapes
1323                .iter()
1324                .any(|s| matches!(s, crate::Shape::Array(_))),
1325            "expected an Array shape in defs.shapes: {:?}",
1326            defs.shapes,
1327        );
1328    }
1329
1330    #[test]
1331    fn deduplicates_identical_object_shapes() {
1332        let ta = typed_ast("let a = { x: 1 }; let b = { x: 2 };");
1333        let defs = PackageDeclaration::from_typed_ast(&ta);
1334        let object_count = defs
1335            .shapes
1336            .iter()
1337            .filter(|s| matches!(s, crate::Shape::Object { .. }))
1338            .count();
1339        assert_eq!(
1340            object_count, 1,
1341            "expected one Object entry, got {object_count}"
1342        );
1343    }
1344
1345    #[test]
1346    fn distinguishes_object_shapes_with_different_fields() {
1347        let ta = typed_ast("let a = { x: 1 }; let b = { y: 2 };");
1348        let defs = PackageDeclaration::from_typed_ast(&ta);
1349        let object_count = defs
1350            .shapes
1351            .iter()
1352            .filter(|s| matches!(s, crate::Shape::Object { .. }))
1353            .count();
1354        assert_eq!(object_count, 2);
1355    }
1356
1357    #[test]
1358    fn canonical_shape_is_stable_across_modules() {
1359        let ta_a = typed_ast("let p = { x: 1, y: 2 };");
1360        let ta_b = typed_ast("let q = { y: 2, x: 1 };");
1361        let defs_a = PackageDeclaration::from_typed_ast(&ta_a);
1362        let defs_b = PackageDeclaration::from_typed_ast(&ta_b);
1363        let key_a = defs_a
1364            .shapes
1365            .iter()
1366            .find_map(|s| match s {
1367                crate::Shape::Object { .. } => Some(s.canonical_display()),
1368                _ => None,
1369            })
1370            .expect("object shape in defs_a");
1371        let key_b = defs_b
1372            .shapes
1373            .iter()
1374            .find_map(|s| match s {
1375                crate::Shape::Object { .. } => Some(s.canonical_display()),
1376                _ => None,
1377            })
1378            .expect("object shape in defs_b");
1379        assert_eq!(key_a, key_b, "canonical key should match across modules");
1380    }
1381
1382    #[test]
1383    fn nested_array_of_object_collects_both() {
1384        let ta = typed_ast("let xs: { x: number }[] = [];");
1385        let defs = PackageDeclaration::from_typed_ast(&ta);
1386        let has_array = defs
1387            .shapes
1388            .iter()
1389            .any(|s| matches!(s, crate::Shape::Array(_)));
1390        let has_object = defs
1391            .shapes
1392            .iter()
1393            .any(|s| matches!(s, crate::Shape::Object { .. }));
1394        assert!(has_array, "expected Array shape");
1395        assert!(has_object, "expected Object shape");
1396    }
1397}
1398
1399#[cfg(test)]
1400mod type_limit_checks {
1401    use std::collections::BTreeMap;
1402
1403    use super::*;
1404    use crate::compiler_limits::MAX_TYPE_DEPTH;
1405    use crate::type_size::TypeTooLarge;
1406
1407    fn nested(depth: u32) -> Type {
1408        (1..depth).fold(Type::Number, |inner, _| Type::Array(Box::new(inner)))
1409    }
1410
1411    fn namespace(name: &str) -> NamespaceSymbol {
1412        NamespaceSymbol {
1413            name: name.into(),
1414            mangled_prefix: crate::mangle::prelude(name),
1415            declaration_span: Span::at(crate::FileId(0)),
1416            values: BTreeMap::new(),
1417            types: BTreeMap::new(),
1418            namespaces: BTreeMap::new(),
1419            doc: None,
1420        }
1421    }
1422
1423    fn alias(ty: Type) -> TypeSymbol {
1424        TypeSymbol {
1425            name: "Deep".into(),
1426            mangled_name: crate::mangle::prelude("Deep"),
1427            declaration_span: Span::at(crate::FileId(0)),
1428            kind: TypeKind::Alias {
1429                generics: Default::default(),
1430                ty,
1431                doc: None,
1432            },
1433        }
1434    }
1435
1436    #[test]
1437    fn namespace_cleanup_can_run_without_allocating_a_frontier() {
1438        let mut child = namespace("leaf");
1439        for _ in 0..1_000 {
1440            let mut parent = namespace("branch");
1441            parent.namespaces.insert("child".into(), child);
1442            parent
1443                .namespaces
1444                .insert("sibling".into(), namespace("sibling"));
1445            child = parent;
1446        }
1447        drop_namespace_children_without_allocation(&mut child.namespaces);
1448        assert!(child.namespaces.is_empty());
1449    }
1450
1451    #[test]
1452    fn a_type_in_a_nested_namespace_is_checked() {
1453        crate::type_size::tests::on_compiler_stack(|| {
1454            let mut inner = namespace("Inner");
1455            inner
1456                .types
1457                .insert("Deep".into(), alias(nested(MAX_TYPE_DEPTH + 1)));
1458            let mut outer = namespace("Outer");
1459            outer.namespaces.insert("Inner".into(), inner);
1460            let mut declaration = PackageDeclaration::with_package("dep");
1461            assert_eq!(declaration.check_type_limits(), Ok(()));
1462            declaration.namespaces.insert("Outer".into(), outer);
1463            assert_eq!(declaration.check_type_limits(), Err(TypeTooLarge::Depth));
1464        });
1465    }
1466
1467    #[test]
1468    fn runtime_declarations_are_checked() {
1469        crate::type_size::tests::on_compiler_stack(|| {
1470            let mut declaration = PackageDeclaration::with_package("dep");
1471            declaration.runtime_functions.insert(
1472                crate::mangle::prelude("f"),
1473                RuntimeFunction {
1474                    params: vec![nested(MAX_TYPE_DEPTH)],
1475                    ret: Type::Void,
1476                },
1477            );
1478            declaration
1479                .runtime_types
1480                .insert("Deep".into(), alias(nested(MAX_TYPE_DEPTH)));
1481            assert_eq!(declaration.check_type_limits(), Ok(()));
1482            declaration
1483                .runtime_globals
1484                .insert(crate::mangle::prelude("g"), nested(MAX_TYPE_DEPTH + 1));
1485            assert_eq!(declaration.check_type_limits(), Err(TypeTooLarge::Depth));
1486        });
1487    }
1488}
1489
1490#[cfg(test)]
1491mod non_finite_artifact_roundtrip {
1492    use super::{DefaultValue, EnumVariantValue, TypeKind};
1493    use crate::Type;
1494    use crate::types::LiteralF64;
1495
1496    /// The failure [`crate::artifact_f64`] exists to prevent, at the site that
1497    /// makes it reachable from source: `Infinity` is a valid parameter default.
1498    #[test]
1499    fn non_finite_defaults_survive_json() {
1500        for value in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
1501            let json = serde_json::to_string(&DefaultValue::Number(value)).unwrap();
1502            assert!(
1503                !json.contains("null"),
1504                "non-finite default serialized to null: {json}"
1505            );
1506            let DefaultValue::Number(back) = serde_json::from_str(&json).unwrap() else {
1507                panic!("round-trip changed the variant: {json}");
1508            };
1509            assert_eq!(back.to_bits(), value.to_bits(), "round-trip lost {value}");
1510        }
1511    }
1512
1513    #[test]
1514    fn finite_defaults_still_serialize_as_numbers() {
1515        let json = serde_json::to_string(&DefaultValue::Number(1.5)).unwrap();
1516        assert_eq!(json, r#"{"Number":1.5}"#);
1517        let DefaultValue::Number(back) = serde_json::from_str(&json).unwrap() else {
1518            panic!("round-trip changed the variant");
1519        };
1520        assert_eq!(back, 1.5);
1521    }
1522
1523    #[test]
1524    fn non_finite_literal_types_survive_json() {
1525        let ty = Type::NumberLiteral(LiteralF64(f64::INFINITY));
1526        let json = serde_json::to_string(&ty).unwrap();
1527        assert_eq!(serde_json::from_str::<Type>(&json).unwrap(), ty);
1528    }
1529
1530    #[test]
1531    fn non_finite_enum_values_survive_json() {
1532        let value = EnumVariantValue::Number(f64::INFINITY);
1533        let json = serde_json::to_string(&value).unwrap();
1534        assert_eq!(
1535            serde_json::from_str::<EnumVariantValue>(&json).unwrap(),
1536            value
1537        );
1538    }
1539
1540    /// The fourth persisted `f64`, reached by `1e400` in an enum initializer.
1541    #[test]
1542    fn non_finite_enum_variants_survive_json() {
1543        let kind = TypeKind::NumberEnum {
1544            variants: vec![
1545                ("Huge".to_string(), f64::INFINITY),
1546                ("One".to_string(), 1.0),
1547            ],
1548            doc: None,
1549        };
1550        let json = serde_json::to_string(&kind).unwrap();
1551        assert!(
1552            json.contains(r#"["Huge","Infinity"]"#),
1553            "non-finite enum variant did not survive serialization: {json}"
1554        );
1555        let TypeKind::NumberEnum { variants, .. } = serde_json::from_str(&json).unwrap() else {
1556            panic!("round-trip changed the variant: {json}");
1557        };
1558        assert_eq!(variants[0].1.to_bits(), f64::INFINITY.to_bits());
1559        assert_eq!(variants[1].1, 1.0);
1560    }
1561}