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