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