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            params.iter().for_each(|param| visit(&param.ty));
358            visit(ret);
359            if let Some(predicate) = type_predicate {
360                visit(&predicate.asserted_type);
361            }
362        }
363        ValueKind::Let { ty, doc: _ } | ValueKind::Const { ty, doc: _ } => visit(ty),
364    }
365}
366
367fn type_symbol_types(symbol: &TypeSymbol, visit: &mut dyn FnMut(&Type)) {
368    match &symbol.kind {
369        TypeKind::Interface {
370            generics: _,
371            methods,
372            properties,
373            index,
374            dispatch: _,
375            doc: _,
376        } => {
377            methods
378                .values()
379                .for_each(|method| method_types(method, visit));
380            properties.values().for_each(|property| visit(&property.ty));
381            if let Some(index) = index {
382                visit(&index.value);
383            }
384        }
385        TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
386        TypeKind::Alias {
387            generics: _,
388            ty,
389            doc: _,
390        } => visit(ty),
391        TypeKind::Class {
392            generics: _,
393            fields,
394            narrowing_checks,
395            methods,
396            method_visibility: _,
397            accessors,
398            constructor,
399            constructor_visibility: _,
400            statics,
401            static_visibility: _,
402            static_fields,
403            extends,
404            implements: _,
405            doc: _,
406        } => {
407            fields
408                .values()
409                .chain(static_fields.values())
410                .for_each(|field| visit(&field.ty));
411            for check in narrowing_checks.values() {
412                narrowing_check_types(check, visit);
413            }
414            methods
415                .values()
416                .chain(statics.values())
417                .for_each(|method| method_types(method, visit));
418            for accessor in accessors {
419                match accessor {
420                    AccessorSig::Getter { name: _, ret_ty } => visit(ret_ty),
421                    AccessorSig::Setter { name: _, param } => visit(&param.ty),
422                }
423            }
424            constructor.iter().for_each(|param| visit(&param.ty));
425            if let Some(extends) = extends {
426                extends.args.iter().for_each(&mut *visit);
427            }
428        }
429    }
430}
431
432fn narrowing_check_types(check: &crate::FieldNarrowingCheck, visit: &mut dyn FnMut(&Type)) {
433    let crate::FieldNarrowingCheck {
434        declaration: _,
435        test,
436        minimal_test_target,
437        message: _,
438    } = check;
439    if let Some(target) = minimal_test_target {
440        visit(target);
441    }
442    match test {
443        crate::FieldNarrowingTest::Shape(ty) => visit(ty),
444        crate::FieldNarrowingTest::Interface(interface) => {
445            let crate::InterfaceNarrowingTest {
446                index,
447                members,
448                methods: _,
449                non_shape_carriers,
450                shape_allowed: _,
451                nullable: _,
452                undefined: _,
453            } = interface;
454            if let Some(index) = index {
455                visit(&index.value);
456            }
457            members.values().for_each(|member| visit(&member.ty));
458            for carrier in non_shape_carriers {
459                match carrier {
460                    crate::InterfaceCarrier::Array(element)
461                    | crate::InterfaceCarrier::Set(element) => visit(element),
462                    crate::InterfaceCarrier::Map(key, value) => {
463                        visit(key);
464                        visit(value);
465                    }
466
467                    crate::InterfaceCarrier::Number
468                    | crate::InterfaceCarrier::Boolean
469                    | crate::InterfaceCarrier::String
470                    | crate::InterfaceCarrier::BigInt
471                    | crate::InterfaceCarrier::Uint8Array
472                    | crate::InterfaceCarrier::ArrayAny
473                    | crate::InterfaceCarrier::MapAny
474                    | crate::InterfaceCarrier::SetAny
475                    | crate::InterfaceCarrier::RegExp
476                    | crate::InterfaceCarrier::RegExpMatch
477                    | crate::InterfaceCarrier::RegExpMatchArray
478                    | crate::InterfaceCarrier::TemporalInstant
479                    | crate::InterfaceCarrier::TemporalDuration
480                    | crate::InterfaceCarrier::TemporalZonedDateTime
481                    | crate::InterfaceCarrier::TemporalPlainDate
482                    | crate::InterfaceCarrier::TemporalPlainTime
483                    | crate::InterfaceCarrier::TemporalPlainDateTime
484                    | crate::InterfaceCarrier::TemporalPlainYearMonth
485                    | crate::InterfaceCarrier::TemporalPlainMonthDay
486                    | crate::InterfaceCarrier::ObjectShape
487                    | crate::InterfaceCarrier::Url
488                    | crate::InterfaceCarrier::FsStat
489                    | crate::InterfaceCarrier::FsPeek
490                    | crate::InterfaceCarrier::FsDirEntry
491                    | crate::InterfaceCarrier::FsInfo
492                    | crate::InterfaceCarrier::FsMountInfo
493                    | crate::InterfaceCarrier::FsFileWriter
494                    | crate::InterfaceCarrier::HttpResponse
495                    | crate::InterfaceCarrier::HttpDownloadResult
496                    | crate::InterfaceCarrier::SessionEntry
497                    | crate::InterfaceCarrier::SessionPage => {}
498                }
499            }
500        }
501        crate::FieldNarrowingTest::NonNull
502        | crate::FieldNarrowingTest::Substituted
503        | crate::FieldNarrowingTest::Representation => {}
504    }
505}
506
507fn method_types(method: &MethodSig, visit: &mut dyn FnMut(&Type)) {
508    let MethodSig {
509        optional: _,
510        generics: _,
511        params,
512        ret,
513        predicate,
514        doc: _,
515    } = method;
516    params.iter().for_each(|param| visit(&param.ty));
517    visit(ret);
518    if let Some(predicate) = predicate {
519        visit(&predicate.asserted_type);
520    }
521}
522
523#[derive(Default)]
524struct PackageShapeCollector {
525    shapes: Vec<Shape>,
526    seen: BTreeSet<Shape>,
527}
528
529impl PackageShapeCollector {
530    fn collect_value(&mut self, value: &ValueSymbol) {
531        match &value.kind {
532            ValueKind::Function {
533                params,
534                ret,
535                type_predicate,
536                ..
537            } => {
538                for param in params {
539                    self.collect_type(&param.ty);
540                }
541                self.collect_type(ret);
542                if let Some(predicate) = type_predicate {
543                    self.collect_type(&predicate.asserted_type);
544                }
545            }
546            ValueKind::Let { ty, .. } | ValueKind::Const { ty, .. } => self.collect_type(ty),
547        }
548    }
549
550    fn collect_type_symbol(&mut self, ty: &TypeSymbol) {
551        match &ty.kind {
552            TypeKind::Interface {
553                methods,
554                properties,
555                index,
556                ..
557            } => {
558                if let Some(index) = index {
559                    self.collect_type(&index.value);
560                }
561                for method in methods.values() {
562                    for param in &method.params {
563                        self.collect_type(&param.ty);
564                    }
565                    self.collect_type(&method.ret);
566                    if let Some(predicate) = &method.predicate {
567                        self.collect_type(&predicate.asserted_type);
568                    }
569                }
570                for property in properties.values() {
571                    self.collect_type(&property.ty);
572                }
573            }
574            TypeKind::Class {
575                fields,
576                methods,
577                accessors,
578                constructor,
579                ..
580            } => {
581                for field in fields.values() {
582                    self.collect_type(&field.ty);
583                }
584                for method in methods.values() {
585                    for param in &method.params {
586                        self.collect_type(&param.ty);
587                    }
588                    self.collect_type(&method.ret);
589                }
590                for acc in accessors {
591                    match acc {
592                        AccessorSig::Getter { ret_ty, .. } => self.collect_type(ret_ty),
593                        AccessorSig::Setter { param, .. } => self.collect_type(&param.ty),
594                    }
595                }
596                for param in constructor {
597                    self.collect_type(&param.ty);
598                }
599            }
600            TypeKind::Alias { ty, .. } => self.collect_type(ty),
601            TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
602        }
603    }
604
605    fn collect_namespace(&mut self, namespace: &NamespaceSymbol) {
606        for value in namespace.values.values() {
607            self.collect_value(value);
608        }
609        for ty in namespace.types.values() {
610            self.collect_type_symbol(ty);
611        }
612        for child in namespace.namespaces.values() {
613            self.collect_namespace(child);
614        }
615    }
616
617    fn collect_type(&mut self, ty: &Type) {
618        match ty {
619            Type::Object { fields, .. } => {
620                self.collect_shape(ty);
621                for field in fields.values() {
622                    self.collect_type(&field.ty);
623                }
624            }
625            Type::Array(elem) => {
626                self.collect_shape(ty);
627                self.collect_type(elem);
628            }
629            Type::Tuple(elements) => {
630                self.collect_shape(ty);
631                for elem in elements {
632                    self.collect_type(elem);
633                }
634            }
635            Type::Union(members) => {
636                self.collect_shape(ty);
637                for member in members {
638                    self.collect_type(member);
639                }
640            }
641            Type::Function { params, ret, .. } => {
642                for param in params {
643                    self.collect_type(param);
644                }
645                self.collect_type(ret);
646            }
647            Type::InterfaceRef { args, .. }
648            | Type::ClassRef { args, .. }
649            | Type::AliasRef { args, .. } => {
650                for arg in args {
651                    self.collect_type(arg);
652                }
653            }
654            Type::Alias { ty, .. } | Type::Refined { ty, .. } | Type::Readonly(ty) => {
655                self.collect_type(ty);
656            }
657            Type::Number
658            | Type::BigInt
659            | Type::BigIntLiteral(_)
660            | Type::NumberLiteral(_)
661            | Type::String
662            | Type::StringLiteral(_)
663            | Type::Uint8Array
664            | Type::Boolean
665            | Type::BooleanLiteral(_)
666            | Type::Null
667            | Type::Undefined
668            | Type::Void
669            | Type::Never
670            | Type::Unknown
671            | Type::NonPrimitive
672            | Type::Error
673            | Type::TypeVar(_)
674            | Type::GenericParam { .. }
675            | Type::NumberEnum { .. }
676            | Type::StringEnum { .. } => {}
677        }
678    }
679
680    fn collect_shape(&mut self, ty: &Type) {
681        let Some(shape) = Shape::from_type(ty) else {
682            return;
683        };
684        if self.seen.insert(shape.clone()) {
685            self.shapes.push(shape);
686        }
687    }
688}
689
690#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
691pub struct ValueSymbol {
692    /// The symbol's own (unaliased) name within its package.
693    pub name: String,
694    /// The key codegen uses for cross-module symbol lookup.
695    pub mangled_name: crate::MangledName,
696    pub declaration_span: Span,
697    pub kind: ValueKind,
698}
699
700#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
701pub struct Param {
702    pub name: String,
703    pub ty: Type,
704    pub default: Option<DefaultValue>,
705    /// Whether a caller may omit the argument. Signatures the typechecker
706    /// builds from source set it for `?` and for defaulted parameters alike,
707    /// but a host declaration may give only `default`, so readers ask
708    /// [`Param::is_omittable`], which covers both.
709    #[serde(default)]
710    pub optional: bool,
711    /// Callee sees `T[]`; call-site collects trailing args into a fresh array.
712    pub rest: bool,
713}
714
715impl Param {
716    /// Whether a caller may leave the argument out: declared `?`, or defaulted.
717    pub fn is_omittable(&self) -> bool {
718        self.optional || self.default.is_some()
719    }
720}
721
722/// Optional fixed parameters at the end of a signature, excluding its rest slot.
723pub(crate) fn optional_parameter_count(params: &[Param]) -> usize {
724    params
725        .iter()
726        .rev()
727        .skip_while(|param| param.rest)
728        .take_while(|param| param.is_omittable())
729        .count()
730}
731
732impl Param {
733    pub fn new(name: impl Into<String>, ty: Type) -> Self {
734        Self {
735            name: name.into(),
736            ty,
737            default: None,
738            optional: false,
739            rest: false,
740        }
741    }
742
743    pub fn with_default(name: impl Into<String>, ty: Type, default: DefaultValue) -> Self {
744        Self {
745            name: name.into(),
746            ty,
747            default: Some(default),
748            optional: true,
749            rest: false,
750        }
751    }
752
753    /// The caller may omit this parameter, supplying `undefined`.
754    pub fn optional(name: impl Into<String>, ty: Type) -> Self {
755        Self {
756            optional: true,
757            ..Self::new(name, ty)
758        }
759    }
760
761    /// `ty` is the array type `T[]`. Must be last; cannot have a default.
762    pub fn rest(name: impl Into<String>, ty: Type) -> Self {
763        Self {
764            name: name.into(),
765            ty,
766            default: None,
767            optional: false,
768            rest: true,
769        }
770    }
771
772    /// Empty name signals "positional" to the diagnostic lifter.
773    pub fn anon(ty: Type) -> Self {
774        Self {
775            name: String::new(),
776            ty,
777            default: None,
778            optional: false,
779            rest: false,
780        }
781    }
782}
783
784/// Lower a body-pass `TypedParam` to an exported `Param`, preserving `rest` and
785/// the resolved default so cross-module callers can omit the argument.
786pub(crate) fn param_from_typed(p: &crate::TypedParam) -> Param {
787    Param {
788        name: p.name.name.clone(),
789        ty: p.ty.clone(),
790        default: p.default.clone(),
791        optional: p.optional,
792        rest: p.rest,
793    }
794}
795
796/// Restricted to shapes materializable without re-evaluating arbitrary source.
797/// Re-evaluated per call (matching JS/TS) — a fresh `TypedExpr` is spliced at each call site.
798#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
799pub enum DefaultValue {
800    Undefined,
801    Number(#[serde(with = "crate::artifact_f64")] f64),
802    /// A number for an omitted argument that an explicit `undefined` does not
803    /// share, as in JavaScript: `splice(1)` removes to the end, while
804    /// `splice(1, undefined)` converts `undefined` to `0` and removes nothing.
805    OmittedNumber {
806        #[serde(with = "crate::artifact_f64")]
807        omitted: f64,
808        #[serde(with = "crate::artifact_f64")]
809        undefined: f64,
810    },
811    String(String),
812    Boolean(bool),
813    Null,
814    /// `[]` — the parameter's resolved type must be `Type::Array(_)`;
815    /// signature-time validation checks this.
816    EmptyArray,
817    /// `{}` — the parameter's resolved type must be `Type::Object { .. }`. Synthesizes an
818    /// empty object literal; absent optional fields serialize away. Used for MCP tools
819    /// whose input schema marks nothing required, so a zero-arg call type-checks.
820    EmptyObject,
821    /// Codegen emits a `GlobalGet` at each fill-in site.
822    GlobalConst(crate::MangledName),
823    /// Fields mirror `TypedExprKind::{Number,String}EnumMember` so the call-site synthesizer can build that node verbatim.
824    EnumVariant {
825        enum_mangled: crate::MangledName,
826        variant: String,
827        value: EnumVariantValue,
828    },
829}
830
831#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
832pub enum EnumVariantValue {
833    Number(#[serde(with = "crate::artifact_f64")] f64),
834    String(String),
835}
836
837#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
838pub enum ValueKind {
839    Function {
840        /// Names appear inside `params`/`ret` as `Type::Var(name)`, resolved per call site by `TypeParamSubstitution`.
841        generics: Vec<String>,
842        params: Vec<Param>,
843        ret: Type,
844        /// Type-guard (`x is T`); inferer copies this into `Type::Function` at each reference site.
845        type_predicate: Option<crate::TypePredicate>,
846        doc: Option<crate::DocComment>,
847    },
848    Let {
849        /// Resolved type of the binding. `Type::Error` when the annotation
850        /// was missing or unresolvable — the originating diagnostic is in
851        /// the diagnostics list.
852        ty: Type,
853        doc: Option<crate::DocComment>,
854    },
855    Const {
856        ty: Type,
857        doc: Option<crate::DocComment>,
858    },
859}
860
861#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
862pub struct TypeSymbol {
863    pub name: String,
864    /// Same role as `ValueSymbol::mangled_name`, for the type namespace.
865    pub mangled_name: crate::MangledName,
866    pub declaration_span: Span,
867    pub kind: TypeKind,
868}
869
870// `Interface` carries an optional `DocComment` which makes
871// the enum non-trivial in size; the enum is rarely instantiated in
872// hot paths so the variance is acceptable.
873#[allow(clippy::large_enum_variant)]
874#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
875pub enum TypeKind {
876    /// Single declaration site per name — no reopening, no `extends` in v1.
877    /// Codegen has no Wasm representation for the interface itself; it only drives method dispatch.
878    Interface {
879        generics: Vec<String>,
880        methods: BTreeMap<String, MethodSig>,
881        /// Read via `expr.name` (not `expr.name()`). Writable unless the
882        /// declaration carries a `readonly` modifier (`PropertySig.readonly`).
883        properties: BTreeMap<String, PropertySig>,
884        index: Option<crate::IndexSignature>,
885        dispatch: Dispatch,
886        doc: Option<crate::DocComment>,
887    },
888    /// Auto-numbered at bind time (source AST stores `None`; bound form holds the resolved value).
889    NumberEnum {
890        #[serde(with = "crate::artifact_f64::pairs")]
891        variants: Vec<(String, f64)>,
892        doc: Option<crate::DocComment>,
893    },
894    /// Every variant must have an explicit initializer; auto-numbering doesn't apply.
895    StringEnum {
896        variants: Vec<(String, String)>,
897        doc: Option<crate::DocComment>,
898    },
899    /// Stored `ty` is the fully resolved alias body. At use sites the inferer wraps the result in
900    /// `Type::Alias { name, args, ty }` so the alias label flows through `Display`.
901    Alias {
902        generics: Vec<String>,
903        ty: Type,
904        doc: Option<crate::DocComment>,
905    },
906    /// A `class` declaration. Nominal (`Type::ClassRef` keys on the symbol's mangled name).
907    /// Single inheritance via `extends`; `implements` is structural conformance only.
908    /// Privacy is enforced at the access site (module-scoped), not by stripping here.
909    /// Methods are always vtable-dispatched (overrides slot into the inherited index),
910    /// so there is no per-class `dispatch` — unlike `Interface`, where the prelude opts
911    /// into direct/static dispatch.
912    Class {
913        /// Class-level type parameters, erased at codegen (every `T`-typed slot
914        /// is a boxed value slot). Substituted at use sites from the
915        /// `ClassRef`'s args.
916        generics: Vec<String>,
917        fields: BTreeMap<String, FieldSig>,
918        /// Runtime read guards for fields whose declaration narrows an inherited
919        /// slot. Carried across packages because the consumer reconstructs the
920        /// class layout and emits its reads independently.
921        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
922        narrowing_checks: BTreeMap<String, crate::FieldNarrowingCheck>,
923        methods: BTreeMap<String, MethodSig>,
924        /// Per-method visibility, keyed alongside `methods` (kept separate so
925        /// `MethodSig` stays shared with interfaces, which have no visibility).
926        method_visibility: BTreeMap<String, crate::Visibility>,
927        /// Accessor functions (`get`/`set`) — one entry each, mirroring the typed
928        /// AST. A property's read type (getter) and write type (setter) are
929        /// independent; the property itself is also in `fields` (its read type) for
930        /// the type system. Carries the setter's parameter name + type so the
931        /// public surface (docs, `.d.ts`) renders the full signature.
932        accessors: Vec<AccessorSig>,
933        /// Constructor parameter signature (no return type).
934        constructor: Vec<Param>,
935        /// A `private` constructor may be called, and the class extended, only in
936        /// the module that declares the class.
937        #[serde(default, skip_serializing_if = "crate::Visibility::is_public")]
938        constructor_visibility: crate::Visibility,
939        /// Static methods — self-less functions dispatched by name on the class
940        /// object (`Class#static#name`), never through the vtable. Inherited down
941        /// the `extends` chain by name resolution at the use site.
942        #[serde(default)]
943        statics: BTreeMap<String, MethodSig>,
944        /// Per-static-method visibility, mirroring `method_visibility`.
945        #[serde(default)]
946        static_visibility: BTreeMap<String, crate::Visibility>,
947        /// Static fields, backed by module globals (`Class#static#name`). `readonly`
948        /// is declaration-driven; a mutable one is writable through the class name.
949        #[serde(default)]
950        static_fields: BTreeMap<String, FieldSig>,
951        /// Resolved parent class, if any.
952        extends: Option<ClassExtends>,
953        /// Resolved interfaces this class declares it implements.
954        implements: Vec<crate::MangledName>,
955        doc: Option<crate::DocComment>,
956    },
957    // `String` is absent — it's an intrinsic declared per-consumer via `declare_intrinsic_types`.
958}
959
960/// A class's resolved `extends` clause. Parent and type args are one fact:
961/// every chain walker that hops to the parent must zip `args` against the
962/// parent's generics, so they travel together.
963#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
964pub struct ClassExtends {
965    pub parent: crate::MangledName,
966    /// Type args applied to the parent's generics; may mention the child's own
967    /// generics. Empty for a non-generic parent.
968    pub args: Vec<Type>,
969}
970
971impl ClassExtends {
972    /// Extends a non-generic parent.
973    pub fn plain(parent: crate::MangledName) -> Self {
974        Self {
975            parent,
976            args: Vec::new(),
977        }
978    }
979}
980
981#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
982pub enum Dispatch {
983    /// Codegen pushes the receiver as the first argument before calling the prelude wrapper.
984    Direct,
985    /// Codegen evaluates the receiver (for side effects) and drops it, then calls with only user args.
986    Static,
987    /// `call_ref` via a funcref loaded from the receiver's vtable slot.
988    VTable,
989}
990
991#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
992pub struct MethodSig {
993    /// Declared `m?(…)`: the member may be absent, so a call goes through `?.`.
994    #[serde(default)]
995    pub optional: bool,
996    pub generics: Vec<String>,
997    pub params: Vec<Param>,
998    pub ret: Type,
999    /// Type-guard predicate (`x is T`); `None` for ordinary methods.
1000    pub predicate: Option<crate::TypePredicate>,
1001    pub doc: Option<crate::DocComment>,
1002}
1003
1004/// One accessor function on a class property. Getter and setter are independent
1005/// (TS 4.3+): a property may declare a getter, a setter, or both, and their types
1006/// need not match. The setter carries its parameter (name + write type).
1007#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1008pub enum AccessorSig {
1009    Getter { name: String, ret_ty: Type },
1010    Setter { name: String, param: Param },
1011}
1012
1013impl AccessorSig {
1014    pub fn name(&self) -> &str {
1015        match self {
1016            AccessorSig::Getter { name, .. } | AccessorSig::Setter { name, .. } => name,
1017        }
1018    }
1019}
1020
1021/// `readonly` mirrors the declared modifier and forbids writes through the
1022/// interface (shallow — it does not affect assignability). Optional properties
1023/// widen reads to `T | undefined`.
1024///
1025/// `intrinsic` marks a member codegen emits inline as an instruction sequence
1026/// (`String#length`/`Array#length` → payload `struct.get` + `array.len`): no
1027/// getter exists to import, and the emitter owns its lowering.
1028#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1029pub struct PropertySig {
1030    pub ty: Type,
1031    pub readonly: bool,
1032    pub optional: bool,
1033    #[serde(default)]
1034    pub intrinsic: bool,
1035    pub doc: Option<crate::DocComment>,
1036}
1037
1038/// A class instance field. Unlike [`PropertySig`], a field may be mutable and carries
1039/// `visibility`; `readonly` fields are writeable only inside the declaring constructor.
1040/// Optional fields widen reads to `T | undefined`.
1041#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1042pub struct FieldSig {
1043    pub ty: Type,
1044    pub visibility: crate::Visibility,
1045    pub readonly: bool,
1046    pub optional: bool,
1047    /// Declared as a bodyless optional method (`f?(): T;`), which a subclass
1048    /// may override with a method.
1049    #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1050    pub method: bool,
1051    pub doc: Option<crate::DocComment>,
1052}
1053
1054/// No runtime representation; member resolution is fully static.
1055/// Recursive derived operations require the containing declaration's successful
1056/// [`PackageDeclaration::check_type_limits`] check since its last mutation.
1057/// Namespaced types are also mirrored into `PackageDeclaration::types` under their full dotted key.
1058#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1059pub struct NamespaceSymbol {
1060    pub name: String,
1061    /// Shared by every export of this namespace; children extend it via `mangle::extend`.
1062    pub mangled_prefix: crate::MangledName,
1063    /// Synthetic span for prelude namespaces.
1064    pub declaration_span: Span,
1065    pub values: BTreeMap<String, ValueSymbol>,
1066    /// Also mirrored into `PackageDeclaration::types` under the full dotted key for `lookup_named_type`.
1067    pub types: BTreeMap<String, TypeSymbol>,
1068    pub namespaces: BTreeMap<String, NamespaceSymbol>,
1069    pub doc: Option<crate::DocComment>,
1070}
1071
1072impl Drop for NamespaceSymbol {
1073    fn drop(&mut self) {
1074        drop_namespace_children(&mut self.namespaces);
1075    }
1076}
1077
1078fn drop_namespace_children(namespaces: &mut BTreeMap<String, NamespaceSymbol>) {
1079    if namespaces.is_empty() {
1080        return;
1081    }
1082    let mut pending = Vec::new();
1083    if pending.try_reserve(1).is_err() {
1084        drop_namespace_children_without_allocation(namespaces);
1085        return;
1086    }
1087    pending.push(std::mem::take(namespaces).into_values());
1088    while let Some(siblings) = pending.last_mut() {
1089        let Some(mut child) = siblings.next() else {
1090            pending.pop();
1091            continue;
1092        };
1093        if child.namespaces.is_empty() {
1094            continue;
1095        }
1096        if pending.try_reserve(1).is_err() {
1097            drop_namespace_children_without_allocation(&mut child.namespaces);
1098        } else {
1099            pending.push(std::mem::take(&mut child.namespaces).into_values());
1100        }
1101        // Each child now owns no namespaces, so its Drop cannot recurse.
1102    }
1103}
1104
1105fn drop_namespace_children_without_allocation(namespaces: &mut BTreeMap<String, NamespaceSymbol>) {
1106    // Under memory pressure, repeatedly find and remove a leaf. This is slower
1107    // than the iterator stack but needs neither allocation nor native recursion.
1108    while !namespaces.is_empty() {
1109        let mut branch = &mut *namespaces;
1110        loop {
1111            if branch
1112                .first_key_value()
1113                .is_some_and(|(_, child)| child.namespaces.is_empty())
1114            {
1115                branch.pop_first();
1116                break;
1117            }
1118            // The root is nonempty; descent happens only into a nonempty child.
1119            // No callbacks or mutation occur between the check and this access.
1120            branch = &mut branch
1121                .first_entry()
1122                .expect("nonempty namespace branch")
1123                .into_mut()
1124                .namespaces;
1125        }
1126    }
1127}
1128
1129#[cfg(test)]
1130mod tests {
1131    use super::PackageDeclaration;
1132    use crate::{Asi, Token, TokenKind, Type, capture, check, desugar, infer, parse};
1133
1134    fn typed_ast(source: &str) -> crate::TypedAst {
1135        let mut asi = Asi::new(source, crate::FileId(0));
1136        let mut tokens: Vec<Token> = Vec::new();
1137        loop {
1138            let tok = asi.next_token();
1139            let is_eof = matches!(tok.kind, TokenKind::Eof);
1140            tokens.push(tok);
1141            if is_eof {
1142                break;
1143            }
1144        }
1145        let _ = asi.into_diagnostics();
1146        let (ast, _) = parse(source, tokens, crate::FileId(0));
1147        let (prelude_defs, host_defs, _) =
1148            crate::runtime::prelude::cached_runtime_package_declarations();
1149        let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
1150        packages.extend(prelude_defs.iter());
1151        packages.extend(host_defs.iter());
1152        let (mut ta, _) = infer(source, "main", &ast, &packages);
1153        let _ = check(&ta);
1154        ta = capture(ta).unwrap();
1155        ta = desugar(ta, crate::FileId(0)).unwrap();
1156        ta
1157    }
1158
1159    #[test]
1160    fn collects_object_shape_from_let_annotation() {
1161        let ta = typed_ast("let p: { x: number; y: number } = { x: 1, y: 2 };");
1162        let defs = PackageDeclaration::from_typed_ast(&ta);
1163        let object = defs
1164            .shapes
1165            .iter()
1166            .find_map(|s| match s {
1167                crate::Shape::Object { fields, .. } => Some(fields),
1168                _ => None,
1169            })
1170            .expect("object shape in defs.shapes");
1171        assert_eq!(
1172            object.get("x"),
1173            Some(&crate::ObjectField::required(Type::Number)),
1174        );
1175        assert_eq!(
1176            object.get("y"),
1177            Some(&crate::ObjectField::required(Type::Number)),
1178        );
1179    }
1180
1181    #[test]
1182    fn preserves_readonly_and_writable_object_shapes() {
1183        let ta =
1184            typed_ast("let a: { readonly x: number } = { x: 1 }; let b: { x: number } = { x: 2 };");
1185        let defs = PackageDeclaration::from_typed_ast(&ta);
1186        let readonly_flags: std::collections::BTreeSet<bool> = defs
1187            .shapes
1188            .iter()
1189            .filter_map(|shape| {
1190                let crate::Shape::Object { fields, .. } = shape else {
1191                    return None;
1192                };
1193                fields.get("x").map(|field| field.readonly)
1194            })
1195            .collect();
1196        assert_eq!(
1197            readonly_flags,
1198            std::collections::BTreeSet::from([false, true])
1199        );
1200    }
1201
1202    #[test]
1203    fn collects_array_shape_from_let_annotation() {
1204        let ta = typed_ast("let xs: number[] = [1, 2, 3];");
1205        let defs = PackageDeclaration::from_typed_ast(&ta);
1206        assert!(
1207            defs.shapes
1208                .iter()
1209                .any(|s| matches!(s, crate::Shape::Array(_))),
1210            "expected an Array shape in defs.shapes: {:?}",
1211            defs.shapes,
1212        );
1213    }
1214
1215    #[test]
1216    fn deduplicates_identical_object_shapes() {
1217        let ta = typed_ast("let a = { x: 1 }; let b = { x: 2 };");
1218        let defs = PackageDeclaration::from_typed_ast(&ta);
1219        let object_count = defs
1220            .shapes
1221            .iter()
1222            .filter(|s| matches!(s, crate::Shape::Object { .. }))
1223            .count();
1224        assert_eq!(
1225            object_count, 1,
1226            "expected one Object entry, got {object_count}"
1227        );
1228    }
1229
1230    #[test]
1231    fn distinguishes_object_shapes_with_different_fields() {
1232        let ta = typed_ast("let a = { x: 1 }; let b = { y: 2 };");
1233        let defs = PackageDeclaration::from_typed_ast(&ta);
1234        let object_count = defs
1235            .shapes
1236            .iter()
1237            .filter(|s| matches!(s, crate::Shape::Object { .. }))
1238            .count();
1239        assert_eq!(object_count, 2);
1240    }
1241
1242    #[test]
1243    fn canonical_shape_is_stable_across_modules() {
1244        let ta_a = typed_ast("let p = { x: 1, y: 2 };");
1245        let ta_b = typed_ast("let q = { y: 2, x: 1 };");
1246        let defs_a = PackageDeclaration::from_typed_ast(&ta_a);
1247        let defs_b = PackageDeclaration::from_typed_ast(&ta_b);
1248        let key_a = defs_a
1249            .shapes
1250            .iter()
1251            .find_map(|s| match s {
1252                crate::Shape::Object { .. } => Some(s.canonical_display()),
1253                _ => None,
1254            })
1255            .expect("object shape in defs_a");
1256        let key_b = defs_b
1257            .shapes
1258            .iter()
1259            .find_map(|s| match s {
1260                crate::Shape::Object { .. } => Some(s.canonical_display()),
1261                _ => None,
1262            })
1263            .expect("object shape in defs_b");
1264        assert_eq!(key_a, key_b, "canonical key should match across modules");
1265    }
1266
1267    #[test]
1268    fn nested_array_of_object_collects_both() {
1269        let ta = typed_ast("let xs: { x: number }[] = [];");
1270        let defs = PackageDeclaration::from_typed_ast(&ta);
1271        let has_array = defs
1272            .shapes
1273            .iter()
1274            .any(|s| matches!(s, crate::Shape::Array(_)));
1275        let has_object = defs
1276            .shapes
1277            .iter()
1278            .any(|s| matches!(s, crate::Shape::Object { .. }));
1279        assert!(has_array, "expected Array shape");
1280        assert!(has_object, "expected Object shape");
1281    }
1282}
1283
1284#[cfg(test)]
1285mod type_limit_checks {
1286    use std::collections::BTreeMap;
1287
1288    use super::*;
1289    use crate::compiler_limits::MAX_TYPE_DEPTH;
1290    use crate::type_size::TypeTooLarge;
1291
1292    fn nested(depth: u32) -> Type {
1293        (1..depth).fold(Type::Number, |inner, _| Type::Array(Box::new(inner)))
1294    }
1295
1296    fn namespace(name: &str) -> NamespaceSymbol {
1297        NamespaceSymbol {
1298            name: name.into(),
1299            mangled_prefix: crate::mangle::prelude(name),
1300            declaration_span: Span::at(crate::FileId(0)),
1301            values: BTreeMap::new(),
1302            types: BTreeMap::new(),
1303            namespaces: BTreeMap::new(),
1304            doc: None,
1305        }
1306    }
1307
1308    fn alias(ty: Type) -> TypeSymbol {
1309        TypeSymbol {
1310            name: "Deep".into(),
1311            mangled_name: crate::mangle::prelude("Deep"),
1312            declaration_span: Span::at(crate::FileId(0)),
1313            kind: TypeKind::Alias {
1314                generics: Vec::new(),
1315                ty,
1316                doc: None,
1317            },
1318        }
1319    }
1320
1321    #[test]
1322    fn namespace_cleanup_can_run_without_allocating_a_frontier() {
1323        let mut child = namespace("leaf");
1324        for _ in 0..1_000 {
1325            let mut parent = namespace("branch");
1326            parent.namespaces.insert("child".into(), child);
1327            parent
1328                .namespaces
1329                .insert("sibling".into(), namespace("sibling"));
1330            child = parent;
1331        }
1332        drop_namespace_children_without_allocation(&mut child.namespaces);
1333        assert!(child.namespaces.is_empty());
1334    }
1335
1336    #[test]
1337    fn a_type_in_a_nested_namespace_is_checked() {
1338        crate::type_size::tests::on_compiler_stack(|| {
1339            let mut inner = namespace("Inner");
1340            inner
1341                .types
1342                .insert("Deep".into(), alias(nested(MAX_TYPE_DEPTH + 1)));
1343            let mut outer = namespace("Outer");
1344            outer.namespaces.insert("Inner".into(), inner);
1345            let mut declaration = PackageDeclaration::with_package("dep");
1346            assert_eq!(declaration.check_type_limits(), Ok(()));
1347            declaration.namespaces.insert("Outer".into(), outer);
1348            assert_eq!(declaration.check_type_limits(), Err(TypeTooLarge::Depth));
1349        });
1350    }
1351
1352    #[test]
1353    fn runtime_declarations_are_checked() {
1354        crate::type_size::tests::on_compiler_stack(|| {
1355            let mut declaration = PackageDeclaration::with_package("dep");
1356            declaration.runtime_functions.insert(
1357                crate::mangle::prelude("f"),
1358                RuntimeFunction {
1359                    params: vec![nested(MAX_TYPE_DEPTH)],
1360                    ret: Type::Void,
1361                },
1362            );
1363            declaration
1364                .runtime_types
1365                .insert("Deep".into(), alias(nested(MAX_TYPE_DEPTH)));
1366            assert_eq!(declaration.check_type_limits(), Ok(()));
1367            declaration
1368                .runtime_globals
1369                .insert(crate::mangle::prelude("g"), nested(MAX_TYPE_DEPTH + 1));
1370            assert_eq!(declaration.check_type_limits(), Err(TypeTooLarge::Depth));
1371        });
1372    }
1373}
1374
1375#[cfg(test)]
1376mod non_finite_artifact_roundtrip {
1377    use super::{DefaultValue, EnumVariantValue, TypeKind};
1378    use crate::Type;
1379    use crate::types::LiteralF64;
1380
1381    /// The failure [`crate::artifact_f64`] exists to prevent, at the site that
1382    /// makes it reachable from source: `Infinity` is a valid parameter default.
1383    #[test]
1384    fn non_finite_defaults_survive_json() {
1385        for value in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
1386            let json = serde_json::to_string(&DefaultValue::Number(value)).unwrap();
1387            assert!(
1388                !json.contains("null"),
1389                "non-finite default serialized to null: {json}"
1390            );
1391            let DefaultValue::Number(back) = serde_json::from_str(&json).unwrap() else {
1392                panic!("round-trip changed the variant: {json}");
1393            };
1394            assert_eq!(back.to_bits(), value.to_bits(), "round-trip lost {value}");
1395        }
1396    }
1397
1398    #[test]
1399    fn finite_defaults_still_serialize_as_numbers() {
1400        let json = serde_json::to_string(&DefaultValue::Number(1.5)).unwrap();
1401        assert_eq!(json, r#"{"Number":1.5}"#);
1402        let DefaultValue::Number(back) = serde_json::from_str(&json).unwrap() else {
1403            panic!("round-trip changed the variant");
1404        };
1405        assert_eq!(back, 1.5);
1406    }
1407
1408    #[test]
1409    fn non_finite_literal_types_survive_json() {
1410        let ty = Type::NumberLiteral(LiteralF64(f64::INFINITY));
1411        let json = serde_json::to_string(&ty).unwrap();
1412        assert_eq!(serde_json::from_str::<Type>(&json).unwrap(), ty);
1413    }
1414
1415    #[test]
1416    fn non_finite_enum_values_survive_json() {
1417        let value = EnumVariantValue::Number(f64::INFINITY);
1418        let json = serde_json::to_string(&value).unwrap();
1419        assert_eq!(
1420            serde_json::from_str::<EnumVariantValue>(&json).unwrap(),
1421            value
1422        );
1423    }
1424
1425    /// The fourth persisted `f64`, reached by `1e400` in an enum initializer.
1426    #[test]
1427    fn non_finite_enum_variants_survive_json() {
1428        let kind = TypeKind::NumberEnum {
1429            variants: vec![
1430                ("Huge".to_string(), f64::INFINITY),
1431                ("One".to_string(), 1.0),
1432            ],
1433            doc: None,
1434        };
1435        let json = serde_json::to_string(&kind).unwrap();
1436        assert!(
1437            json.contains(r#"["Huge","Infinity"]"#),
1438            "non-finite enum variant did not survive serialization: {json}"
1439        );
1440        let TypeKind::NumberEnum { variants, .. } = serde_json::from_str(&json).unwrap() else {
1441            panic!("round-trip changed the variant: {json}");
1442        };
1443        assert_eq!(variants[0].1.to_bits(), f64::INFINITY.to_bits());
1444        assert_eq!(variants[1].1, 1.0);
1445    }
1446}