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