1use std::collections::{BTreeMap, BTreeSet};
10
11use crate::{Shape, Span, Type, TypedAst};
12use serde::{Deserialize, Serialize};
13
14#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
17pub enum VirtualDispatch {
18 Mcp { server: String },
20 Tools,
22}
23
24impl VirtualDispatch {
25 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 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#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
49pub struct PackageDeclaration {
50 #[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 pub package_name: String,
58 #[serde(default, skip_serializing_if = "Option::is_none")]
64 pub virtual_dispatch: Option<VirtualDispatch>,
65 pub values: BTreeMap<String, ValueSymbol>,
66 pub types: BTreeMap<String, TypeSymbol>,
68 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
71 pub runtime_types: BTreeMap<String, TypeSymbol>,
72 #[serde(default, skip_serializing_if = "BTreeSet::is_empty")]
74 pub runtime_generics: BTreeSet<crate::MangledName>,
75 pub shapes: Vec<crate::Shape>,
77 pub namespaces: BTreeMap<String, NamespaceSymbol>,
79 #[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 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 pub(crate) fn without_finalizer(mut self) -> Self {
108 self.values.remove("finalizer");
109 self
110 }
111
112 pub fn from_typed_ast(ast: &TypedAst) -> Self {
114 let mut defs = PackageDeclaration::with_package(ast.package_name.clone());
115 for f in &ast.functions {
116 if !f.generics.is_empty() {
117 defs.runtime_generics.insert(f.mangled_name.clone());
118 }
119 defs.values
120 .entry(f.name.name.clone())
121 .or_insert_with(|| ValueSymbol {
122 name: f.name.name.clone(),
123 mangled_name: f.mangled_name.clone(),
124 declaration_span: f.name.span,
125 kind: ValueKind::Function {
126 generics: f.generics.clone(),
127 params: f.params.iter().map(param_from_typed).collect(),
128 ret: f.return_type.clone(),
129 type_predicate: f.type_predicate.clone(),
130 doc: f.doc.clone(),
131 },
132 });
133 }
134 for g in &ast.globals {
135 defs.values
136 .entry(g.name.name.clone())
137 .or_insert_with(|| ValueSymbol {
138 name: g.name.name.clone(),
139 mangled_name: g.mangled_name.clone(),
140 declaration_span: g.name.span,
141 kind: match g.kind {
142 crate::GlobalKind::Let => ValueKind::Let {
143 ty: g.ty.clone(),
144 doc: g.doc.clone(),
145 },
146 crate::GlobalKind::Const => ValueKind::Const {
147 ty: g.ty.clone(),
148 doc: g.doc.clone(),
149 },
150 },
151 });
152 }
153 for ty_decl in &ast.types {
155 if let crate::TypedTypeDecl::Alias(alias) = ty_decl {
156 defs.types
157 .entry(alias.name.name.clone())
158 .or_insert_with(|| TypeSymbol {
159 name: alias.name.name.clone(),
160 mangled_name: crate::mangle::package_symbol(
161 &ast.package_name,
162 &alias.name.name,
163 ),
164 declaration_span: alias.name.span,
165 kind: TypeKind::Alias {
166 generics: alias.generics.clone(),
167 ty: alias.ty.clone(),
168 doc: alias.doc.clone(),
169 },
170 });
171 }
172 }
173 defs.refresh_shapes();
174 defs
175 }
176
177 pub fn type_symbol(&self, mangled: &crate::MangledName) -> Option<&TypeSymbol> {
182 self.runtime_types
183 .get(mangled.as_str())
184 .or_else(|| symbol_named(self.types.values(), mangled))
185 .or_else(|| {
186 self.namespaces
187 .values()
188 .find_map(|namespace| namespace_type_symbol(namespace, mangled))
189 })
190 }
191
192 pub fn refresh_shapes(&mut self) {
193 self.shapes = self.collect_shapes();
194 }
195
196 pub fn collect_shapes(&self) -> Vec<Shape> {
197 let mut collector = PackageShapeCollector::default();
198 for value in self.values.values() {
199 collector.collect_value(value);
200 }
201 for ty in self.types.values() {
202 collector.collect_type_symbol(ty);
203 }
204 for namespace in self.namespaces.values() {
205 collector.collect_namespace(namespace);
206 }
207 collector.shapes
208 }
209
210 pub fn check_type_limits(&self) -> Result<(), crate::type_size::TypeTooLarge> {
215 check_namespace_limits(&self.namespaces)?;
216 let mut result = Ok(());
217 self.for_each_type(&mut |ty| {
218 if result.is_ok() {
219 result = crate::type_size::check(ty);
220 }
221 });
222 result
223 }
224
225 fn for_each_type(&self, visit: &mut dyn FnMut(&Type)) {
229 let Self {
230 runtime_functions,
231 runtime_globals,
232 package_name: _,
233 virtual_dispatch: _,
234 values,
235 types,
236 runtime_types,
237 runtime_generics: _,
238 shapes,
239 namespaces,
240 closure_caches: _,
241 } = self;
242 for RuntimeFunction { params, ret } in runtime_functions.values() {
243 params.iter().for_each(&mut *visit);
244 visit(ret);
245 }
246 runtime_globals.values().for_each(&mut *visit);
247 values.values().for_each(|value| value_types(value, visit));
248 types
249 .values()
250 .chain(runtime_types.values())
251 .for_each(|symbol| type_symbol_types(symbol, visit));
252 for shape in shapes {
253 match shape {
254 Shape::Object { fields, index } => {
255 fields.values().for_each(|field| visit(&field.ty));
256 if let Some(index) = index {
257 visit(&index.value);
258 }
259 }
260 Shape::Array(element) => visit(element),
261 Shape::Tuple(members) => {
262 members.iter().for_each(&mut *visit);
263 }
264 Shape::Union(members) => {
265 members.iter().for_each(&mut *visit);
266 }
267 }
268 }
269 let mut pending: Vec<&NamespaceSymbol> = namespaces.values().collect();
270 while let Some(namespace) = pending.pop() {
271 let NamespaceSymbol {
272 name: _,
273 mangled_prefix: _,
274 declaration_span: _,
275 values,
276 types,
277 namespaces,
278 doc: _,
279 } = namespace;
280 values.values().for_each(|value| value_types(value, visit));
281 types
282 .values()
283 .for_each(|symbol| type_symbol_types(symbol, visit));
284 pending.extend(namespaces.values());
285 }
286 }
287}
288
289fn check_namespace_limits(
290 namespaces: &BTreeMap<String, NamespaceSymbol>,
291) -> Result<(), crate::type_size::TypeTooLarge> {
292 use crate::compiler_limits::{
293 MAX_NAMESPACE_DEPTH, MAX_NAMESPACE_NODES, MAX_NAMESPACE_PATH_BYTES,
294 };
295 use crate::type_size::TypeTooLarge;
296
297 let mut pending = vec![(namespaces.iter(), 0usize)];
299 let mut nodes = 0;
300 let mut path_bytes = 0usize;
301 while let Some((siblings, prefix_bytes)) = pending.last_mut() {
302 let Some((name, namespace)) = siblings.next() else {
303 pending.pop();
304 continue;
305 };
306 let path_len = prefix_bytes.saturating_add(name.len());
307 nodes += 1;
308 path_bytes = path_bytes.saturating_add(path_len);
309 for name in namespace.types.keys() {
310 path_bytes =
311 path_bytes.saturating_add(path_len.saturating_add(1).saturating_add(name.len()));
312 if path_bytes > MAX_NAMESPACE_PATH_BYTES {
313 return Err(TypeTooLarge::NamespaceMetadata);
314 }
315 }
316 if nodes > MAX_NAMESPACE_NODES
317 || pending.len() > MAX_NAMESPACE_DEPTH
318 || path_bytes > MAX_NAMESPACE_PATH_BYTES
319 {
320 return Err(TypeTooLarge::NamespaceMetadata);
321 }
322 if !namespace.namespaces.is_empty() {
323 pending.push((namespace.namespaces.iter(), path_len.saturating_add(1)));
324 }
325 }
326 Ok(())
327}
328
329fn namespace_type_symbol<'a>(
330 namespace: &'a NamespaceSymbol,
331 mangled: &crate::MangledName,
332) -> Option<&'a TypeSymbol> {
333 symbol_named(namespace.types.values(), mangled).or_else(|| {
334 namespace
335 .namespaces
336 .values()
337 .find_map(|child| namespace_type_symbol(child, mangled))
338 })
339}
340
341fn symbol_named<'a>(
342 mut symbols: impl Iterator<Item = &'a TypeSymbol>,
343 mangled: &crate::MangledName,
344) -> Option<&'a TypeSymbol> {
345 symbols.find(|symbol| symbol.mangled_name == *mangled)
346}
347
348fn value_types(value: &ValueSymbol, visit: &mut dyn FnMut(&Type)) {
349 match &value.kind {
350 ValueKind::Function {
351 generics: _,
352 params,
353 ret,
354 type_predicate,
355 doc: _,
356 } => {
357 params.iter().for_each(|param| visit(¶m.ty));
358 visit(ret);
359 if let Some(predicate) = type_predicate {
360 visit(&predicate.asserted_type);
361 }
362 }
363 ValueKind::Let { ty, doc: _ } | ValueKind::Const { ty, doc: _ } => visit(ty),
364 }
365}
366
367fn type_symbol_types(symbol: &TypeSymbol, visit: &mut dyn FnMut(&Type)) {
368 match &symbol.kind {
369 TypeKind::Interface {
370 generics: _,
371 methods,
372 properties,
373 index,
374 dispatch: _,
375 doc: _,
376 } => {
377 methods
378 .values()
379 .for_each(|method| method_types(method, visit));
380 properties.values().for_each(|property| visit(&property.ty));
381 if let Some(index) = index {
382 visit(&index.value);
383 }
384 }
385 TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
386 TypeKind::Alias {
387 generics: _,
388 ty,
389 doc: _,
390 } => visit(ty),
391 TypeKind::Class {
392 generics: _,
393 fields,
394 narrowing_checks,
395 methods,
396 method_visibility: _,
397 accessors,
398 constructor,
399 constructor_visibility: _,
400 statics,
401 static_visibility: _,
402 static_fields,
403 extends,
404 implements: _,
405 doc: _,
406 } => {
407 fields
408 .values()
409 .chain(static_fields.values())
410 .for_each(|field| visit(&field.ty));
411 for check in narrowing_checks.values() {
412 narrowing_check_types(check, visit);
413 }
414 methods
415 .values()
416 .chain(statics.values())
417 .for_each(|method| method_types(method, visit));
418 for accessor in accessors {
419 match accessor {
420 AccessorSig::Getter { name: _, ret_ty } => visit(ret_ty),
421 AccessorSig::Setter { name: _, param } => visit(¶m.ty),
422 }
423 }
424 constructor.iter().for_each(|param| visit(¶m.ty));
425 if let Some(extends) = extends {
426 extends.args.iter().for_each(&mut *visit);
427 }
428 }
429 }
430}
431
432fn narrowing_check_types(check: &crate::FieldNarrowingCheck, visit: &mut dyn FnMut(&Type)) {
433 let crate::FieldNarrowingCheck {
434 declaration: _,
435 test,
436 minimal_test_target,
437 message: _,
438 } = check;
439 if let Some(target) = minimal_test_target {
440 visit(target);
441 }
442 match test {
443 crate::FieldNarrowingTest::Shape(ty) => visit(ty),
444 crate::FieldNarrowingTest::Interface(interface) => {
445 let crate::InterfaceNarrowingTest {
446 index,
447 members,
448 methods: _,
449 non_shape_carriers,
450 shape_allowed: _,
451 nullable: _,
452 undefined: _,
453 } = interface;
454 if let Some(index) = index {
455 visit(&index.value);
456 }
457 members.values().for_each(|member| visit(&member.ty));
458 for carrier in non_shape_carriers {
459 match carrier {
460 crate::InterfaceCarrier::Array(element)
461 | crate::InterfaceCarrier::Set(element) => visit(element),
462 crate::InterfaceCarrier::Map(key, value) => {
463 visit(key);
464 visit(value);
465 }
466
467 crate::InterfaceCarrier::Number
468 | crate::InterfaceCarrier::Boolean
469 | crate::InterfaceCarrier::String
470 | crate::InterfaceCarrier::BigInt
471 | crate::InterfaceCarrier::Uint8Array
472 | crate::InterfaceCarrier::ArrayAny
473 | crate::InterfaceCarrier::MapAny
474 | crate::InterfaceCarrier::SetAny
475 | crate::InterfaceCarrier::RegExp
476 | crate::InterfaceCarrier::RegExpMatch
477 | crate::InterfaceCarrier::TemporalInstant
478 | crate::InterfaceCarrier::TemporalDuration
479 | crate::InterfaceCarrier::TemporalZonedDateTime
480 | crate::InterfaceCarrier::TemporalPlainDate
481 | crate::InterfaceCarrier::TemporalPlainTime
482 | crate::InterfaceCarrier::TemporalPlainDateTime
483 | crate::InterfaceCarrier::TemporalPlainYearMonth
484 | crate::InterfaceCarrier::TemporalPlainMonthDay
485 | crate::InterfaceCarrier::ObjectShape
486 | crate::InterfaceCarrier::Url
487 | crate::InterfaceCarrier::FsStat
488 | crate::InterfaceCarrier::FsPeek
489 | crate::InterfaceCarrier::FsDirEntry
490 | crate::InterfaceCarrier::FsInfo
491 | crate::InterfaceCarrier::FsMountInfo
492 | crate::InterfaceCarrier::FsFileWriter
493 | crate::InterfaceCarrier::HttpResponse
494 | crate::InterfaceCarrier::HttpDownloadResult
495 | crate::InterfaceCarrier::SessionEntry
496 | crate::InterfaceCarrier::SessionPage => {}
497 }
498 }
499 }
500 crate::FieldNarrowingTest::NonNull
501 | crate::FieldNarrowingTest::Substituted
502 | crate::FieldNarrowingTest::Representation => {}
503 }
504}
505
506fn method_types(method: &MethodSig, visit: &mut dyn FnMut(&Type)) {
507 let MethodSig {
508 optional: _,
509 generics: _,
510 params,
511 ret,
512 predicate,
513 doc: _,
514 } = method;
515 params.iter().for_each(|param| visit(¶m.ty));
516 visit(ret);
517 if let Some(predicate) = predicate {
518 visit(&predicate.asserted_type);
519 }
520}
521
522#[derive(Default)]
523struct PackageShapeCollector {
524 shapes: Vec<Shape>,
525 seen: BTreeSet<Shape>,
526}
527
528impl PackageShapeCollector {
529 fn collect_value(&mut self, value: &ValueSymbol) {
530 match &value.kind {
531 ValueKind::Function {
532 params,
533 ret,
534 type_predicate,
535 ..
536 } => {
537 for param in params {
538 self.collect_type(¶m.ty);
539 }
540 self.collect_type(ret);
541 if let Some(predicate) = type_predicate {
542 self.collect_type(&predicate.asserted_type);
543 }
544 }
545 ValueKind::Let { ty, .. } | ValueKind::Const { ty, .. } => self.collect_type(ty),
546 }
547 }
548
549 fn collect_type_symbol(&mut self, ty: &TypeSymbol) {
550 match &ty.kind {
551 TypeKind::Interface {
552 methods,
553 properties,
554 index,
555 ..
556 } => {
557 if let Some(index) = index {
558 self.collect_type(&index.value);
559 }
560 for method in methods.values() {
561 for param in &method.params {
562 self.collect_type(¶m.ty);
563 }
564 self.collect_type(&method.ret);
565 if let Some(predicate) = &method.predicate {
566 self.collect_type(&predicate.asserted_type);
567 }
568 }
569 for property in properties.values() {
570 self.collect_type(&property.ty);
571 }
572 }
573 TypeKind::Class {
574 fields,
575 methods,
576 accessors,
577 constructor,
578 ..
579 } => {
580 for field in fields.values() {
581 self.collect_type(&field.ty);
582 }
583 for method in methods.values() {
584 for param in &method.params {
585 self.collect_type(¶m.ty);
586 }
587 self.collect_type(&method.ret);
588 }
589 for acc in accessors {
590 match acc {
591 AccessorSig::Getter { ret_ty, .. } => self.collect_type(ret_ty),
592 AccessorSig::Setter { param, .. } => self.collect_type(¶m.ty),
593 }
594 }
595 for param in constructor {
596 self.collect_type(¶m.ty);
597 }
598 }
599 TypeKind::Alias { ty, .. } => self.collect_type(ty),
600 TypeKind::NumberEnum { .. } | TypeKind::StringEnum { .. } => {}
601 }
602 }
603
604 fn collect_namespace(&mut self, namespace: &NamespaceSymbol) {
605 for value in namespace.values.values() {
606 self.collect_value(value);
607 }
608 for ty in namespace.types.values() {
609 self.collect_type_symbol(ty);
610 }
611 for child in namespace.namespaces.values() {
612 self.collect_namespace(child);
613 }
614 }
615
616 fn collect_type(&mut self, ty: &Type) {
617 match ty {
618 Type::Object { fields, .. } => {
619 self.collect_shape(ty);
620 for field in fields.values() {
621 self.collect_type(&field.ty);
622 }
623 }
624 Type::Array(elem) => {
625 self.collect_shape(ty);
626 self.collect_type(elem);
627 }
628 Type::Tuple(elements) => {
629 self.collect_shape(ty);
630 for elem in elements {
631 self.collect_type(elem);
632 }
633 }
634 Type::Union(members) => {
635 self.collect_shape(ty);
636 for member in members {
637 self.collect_type(member);
638 }
639 }
640 Type::Function { params, ret, .. } => {
641 for param in params {
642 self.collect_type(param);
643 }
644 self.collect_type(ret);
645 }
646 Type::InterfaceRef { args, .. }
647 | Type::ClassRef { args, .. }
648 | Type::AliasRef { args, .. } => {
649 for arg in args {
650 self.collect_type(arg);
651 }
652 }
653 Type::Alias { ty, .. } | Type::Refined { ty, .. } | Type::Readonly(ty) => {
654 self.collect_type(ty);
655 }
656 Type::Number
657 | Type::BigInt
658 | Type::BigIntLiteral(_)
659 | Type::NumberLiteral(_)
660 | Type::String
661 | Type::StringLiteral(_)
662 | Type::Uint8Array
663 | Type::Boolean
664 | Type::BooleanLiteral(_)
665 | Type::Null
666 | Type::Undefined
667 | Type::Void
668 | Type::Never
669 | Type::Unknown
670 | Type::NonPrimitive
671 | Type::Error
672 | Type::TypeVar(_)
673 | Type::GenericParam { .. }
674 | Type::NumberEnum { .. }
675 | Type::StringEnum { .. } => {}
676 }
677 }
678
679 fn collect_shape(&mut self, ty: &Type) {
680 let Some(shape) = Shape::from_type(ty) else {
681 return;
682 };
683 if self.seen.insert(shape.clone()) {
684 self.shapes.push(shape);
685 }
686 }
687}
688
689#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
690pub struct ValueSymbol {
691 pub name: String,
693 pub mangled_name: crate::MangledName,
695 pub declaration_span: Span,
696 pub kind: ValueKind,
697}
698
699#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
700pub struct Param {
701 pub name: String,
702 pub ty: Type,
703 pub default: Option<DefaultValue>,
704 #[serde(default)]
709 pub optional: bool,
710 pub rest: bool,
712}
713
714impl Param {
715 pub fn is_omittable(&self) -> bool {
717 self.optional || self.default.is_some()
718 }
719}
720
721pub(crate) fn optional_parameter_count(params: &[Param]) -> usize {
723 params
724 .iter()
725 .rev()
726 .skip_while(|param| param.rest)
727 .take_while(|param| param.is_omittable())
728 .count()
729}
730
731impl Param {
732 pub fn new(name: impl Into<String>, ty: Type) -> Self {
733 Self {
734 name: name.into(),
735 ty,
736 default: None,
737 optional: false,
738 rest: false,
739 }
740 }
741
742 pub fn with_default(name: impl Into<String>, ty: Type, default: DefaultValue) -> Self {
743 Self {
744 name: name.into(),
745 ty,
746 default: Some(default),
747 optional: true,
748 rest: false,
749 }
750 }
751
752 pub fn optional(name: impl Into<String>, ty: Type) -> Self {
754 Self {
755 optional: true,
756 ..Self::new(name, ty)
757 }
758 }
759
760 pub fn rest(name: impl Into<String>, ty: Type) -> Self {
762 Self {
763 name: name.into(),
764 ty,
765 default: None,
766 optional: false,
767 rest: true,
768 }
769 }
770
771 pub fn anon(ty: Type) -> Self {
773 Self {
774 name: String::new(),
775 ty,
776 default: None,
777 optional: false,
778 rest: false,
779 }
780 }
781}
782
783pub(crate) fn param_from_typed(p: &crate::TypedParam) -> Param {
786 Param {
787 name: p.name.name.clone(),
788 ty: p.ty.clone(),
789 default: p.default.clone(),
790 optional: p.optional,
791 rest: p.rest,
792 }
793}
794
795#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
798pub enum DefaultValue {
799 Undefined,
800 Number(#[serde(with = "crate::artifact_f64")] f64),
801 OmittedNumber {
805 #[serde(with = "crate::artifact_f64")]
806 omitted: f64,
807 #[serde(with = "crate::artifact_f64")]
808 undefined: f64,
809 },
810 String(String),
811 Boolean(bool),
812 Null,
813 EmptyArray,
816 EmptyObject,
820 GlobalConst(crate::MangledName),
822 EnumVariant {
824 enum_mangled: crate::MangledName,
825 variant: String,
826 value: EnumVariantValue,
827 },
828}
829
830#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
831pub enum EnumVariantValue {
832 Number(#[serde(with = "crate::artifact_f64")] f64),
833 String(String),
834}
835
836#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
837pub enum ValueKind {
838 Function {
839 generics: Vec<String>,
841 params: Vec<Param>,
842 ret: Type,
843 type_predicate: Option<crate::TypePredicate>,
845 doc: Option<crate::DocComment>,
846 },
847 Let {
848 ty: Type,
852 doc: Option<crate::DocComment>,
853 },
854 Const {
855 ty: Type,
856 doc: Option<crate::DocComment>,
857 },
858}
859
860#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
861pub struct TypeSymbol {
862 pub name: String,
863 pub mangled_name: crate::MangledName,
865 pub declaration_span: Span,
866 pub kind: TypeKind,
867}
868
869#[allow(clippy::large_enum_variant)]
873#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
874pub enum TypeKind {
875 Interface {
878 generics: Vec<String>,
879 methods: BTreeMap<String, MethodSig>,
880 properties: BTreeMap<String, PropertySig>,
883 index: Option<crate::IndexSignature>,
884 dispatch: Dispatch,
885 doc: Option<crate::DocComment>,
886 },
887 NumberEnum {
889 #[serde(with = "crate::artifact_f64::pairs")]
890 variants: Vec<(String, f64)>,
891 doc: Option<crate::DocComment>,
892 },
893 StringEnum {
895 variants: Vec<(String, String)>,
896 doc: Option<crate::DocComment>,
897 },
898 Alias {
901 generics: Vec<String>,
902 ty: Type,
903 doc: Option<crate::DocComment>,
904 },
905 Class {
912 generics: Vec<String>,
916 fields: BTreeMap<String, FieldSig>,
917 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
921 narrowing_checks: BTreeMap<String, crate::FieldNarrowingCheck>,
922 methods: BTreeMap<String, MethodSig>,
923 method_visibility: BTreeMap<String, crate::Visibility>,
926 accessors: Vec<AccessorSig>,
932 constructor: Vec<Param>,
934 #[serde(default, skip_serializing_if = "crate::Visibility::is_public")]
937 constructor_visibility: crate::Visibility,
938 #[serde(default)]
942 statics: BTreeMap<String, MethodSig>,
943 #[serde(default)]
945 static_visibility: BTreeMap<String, crate::Visibility>,
946 #[serde(default)]
949 static_fields: BTreeMap<String, FieldSig>,
950 extends: Option<ClassExtends>,
952 implements: Vec<crate::MangledName>,
954 doc: Option<crate::DocComment>,
955 },
956 }
958
959#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
963pub struct ClassExtends {
964 pub parent: crate::MangledName,
965 pub args: Vec<Type>,
968}
969
970impl ClassExtends {
971 pub fn plain(parent: crate::MangledName) -> Self {
973 Self {
974 parent,
975 args: Vec::new(),
976 }
977 }
978}
979
980#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
981pub enum Dispatch {
982 Direct,
984 Static,
986 VTable,
988}
989
990#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
991pub struct MethodSig {
992 #[serde(default)]
994 pub optional: bool,
995 pub generics: Vec<String>,
996 pub params: Vec<Param>,
997 pub ret: Type,
998 pub predicate: Option<crate::TypePredicate>,
1000 pub doc: Option<crate::DocComment>,
1001}
1002
1003#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1007pub enum AccessorSig {
1008 Getter { name: String, ret_ty: Type },
1009 Setter { name: String, param: Param },
1010}
1011
1012impl AccessorSig {
1013 pub fn name(&self) -> &str {
1014 match self {
1015 AccessorSig::Getter { name, .. } | AccessorSig::Setter { name, .. } => name,
1016 }
1017 }
1018}
1019
1020#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1028pub struct PropertySig {
1029 pub ty: Type,
1030 pub readonly: bool,
1031 pub optional: bool,
1032 #[serde(default)]
1033 pub intrinsic: bool,
1034 pub doc: Option<crate::DocComment>,
1035}
1036
1037#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1041pub struct FieldSig {
1042 pub ty: Type,
1043 pub visibility: crate::Visibility,
1044 pub readonly: bool,
1045 pub optional: bool,
1046 #[serde(default, skip_serializing_if = "std::ops::Not::not")]
1049 pub method: bool,
1050 pub doc: Option<crate::DocComment>,
1051}
1052
1053#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
1058pub struct NamespaceSymbol {
1059 pub name: String,
1060 pub mangled_prefix: crate::MangledName,
1062 pub declaration_span: Span,
1064 pub values: BTreeMap<String, ValueSymbol>,
1065 pub types: BTreeMap<String, TypeSymbol>,
1067 pub namespaces: BTreeMap<String, NamespaceSymbol>,
1068 pub doc: Option<crate::DocComment>,
1069}
1070
1071impl Drop for NamespaceSymbol {
1072 fn drop(&mut self) {
1073 drop_namespace_children(&mut self.namespaces);
1074 }
1075}
1076
1077fn drop_namespace_children(namespaces: &mut BTreeMap<String, NamespaceSymbol>) {
1078 if namespaces.is_empty() {
1079 return;
1080 }
1081 let mut pending = Vec::new();
1082 if pending.try_reserve(1).is_err() {
1083 drop_namespace_children_without_allocation(namespaces);
1084 return;
1085 }
1086 pending.push(std::mem::take(namespaces).into_values());
1087 while let Some(siblings) = pending.last_mut() {
1088 let Some(mut child) = siblings.next() else {
1089 pending.pop();
1090 continue;
1091 };
1092 if child.namespaces.is_empty() {
1093 continue;
1094 }
1095 if pending.try_reserve(1).is_err() {
1096 drop_namespace_children_without_allocation(&mut child.namespaces);
1097 } else {
1098 pending.push(std::mem::take(&mut child.namespaces).into_values());
1099 }
1100 }
1102}
1103
1104fn drop_namespace_children_without_allocation(namespaces: &mut BTreeMap<String, NamespaceSymbol>) {
1105 while !namespaces.is_empty() {
1108 let mut branch = &mut *namespaces;
1109 loop {
1110 if branch
1111 .first_key_value()
1112 .is_some_and(|(_, child)| child.namespaces.is_empty())
1113 {
1114 branch.pop_first();
1115 break;
1116 }
1117 branch = &mut branch
1120 .first_entry()
1121 .expect("nonempty namespace branch")
1122 .into_mut()
1123 .namespaces;
1124 }
1125 }
1126}
1127
1128#[cfg(test)]
1129mod tests {
1130 use super::PackageDeclaration;
1131 use crate::{Asi, Token, TokenKind, Type, capture, check, desugar, infer, parse};
1132
1133 fn typed_ast(source: &str) -> crate::TypedAst {
1134 let mut asi = Asi::new(source, crate::FileId(0));
1135 let mut tokens: Vec<Token> = Vec::new();
1136 loop {
1137 let tok = asi.next_token();
1138 let is_eof = matches!(tok.kind, TokenKind::Eof);
1139 tokens.push(tok);
1140 if is_eof {
1141 break;
1142 }
1143 }
1144 let _ = asi.into_diagnostics();
1145 let (ast, _) = parse(source, tokens, crate::FileId(0));
1146 let (prelude_defs, host_defs, _) =
1147 crate::runtime::prelude::cached_runtime_package_declarations();
1148 let mut packages = Vec::with_capacity(prelude_defs.len() + host_defs.len());
1149 packages.extend(prelude_defs.iter());
1150 packages.extend(host_defs.iter());
1151 let (mut ta, _) = infer(source, "main", &ast, &packages);
1152 let _ = check(&ta);
1153 ta = capture(ta).unwrap();
1154 ta = desugar(ta, crate::FileId(0)).unwrap();
1155 ta
1156 }
1157
1158 #[test]
1159 fn collects_object_shape_from_let_annotation() {
1160 let ta = typed_ast("let p: { x: number; y: number } = { x: 1, y: 2 };");
1161 let defs = PackageDeclaration::from_typed_ast(&ta);
1162 let object = defs
1163 .shapes
1164 .iter()
1165 .find_map(|s| match s {
1166 crate::Shape::Object { fields, .. } => Some(fields),
1167 _ => None,
1168 })
1169 .expect("object shape in defs.shapes");
1170 assert_eq!(
1171 object.get("x"),
1172 Some(&crate::ObjectField::required(Type::Number)),
1173 );
1174 assert_eq!(
1175 object.get("y"),
1176 Some(&crate::ObjectField::required(Type::Number)),
1177 );
1178 }
1179
1180 #[test]
1181 fn preserves_readonly_and_writable_object_shapes() {
1182 let ta =
1183 typed_ast("let a: { readonly x: number } = { x: 1 }; let b: { x: number } = { x: 2 };");
1184 let defs = PackageDeclaration::from_typed_ast(&ta);
1185 let readonly_flags: std::collections::BTreeSet<bool> = defs
1186 .shapes
1187 .iter()
1188 .filter_map(|shape| {
1189 let crate::Shape::Object { fields, .. } = shape else {
1190 return None;
1191 };
1192 fields.get("x").map(|field| field.readonly)
1193 })
1194 .collect();
1195 assert_eq!(
1196 readonly_flags,
1197 std::collections::BTreeSet::from([false, true])
1198 );
1199 }
1200
1201 #[test]
1202 fn collects_array_shape_from_let_annotation() {
1203 let ta = typed_ast("let xs: number[] = [1, 2, 3];");
1204 let defs = PackageDeclaration::from_typed_ast(&ta);
1205 assert!(
1206 defs.shapes
1207 .iter()
1208 .any(|s| matches!(s, crate::Shape::Array(_))),
1209 "expected an Array shape in defs.shapes: {:?}",
1210 defs.shapes,
1211 );
1212 }
1213
1214 #[test]
1215 fn deduplicates_identical_object_shapes() {
1216 let ta = typed_ast("let a = { x: 1 }; let b = { x: 2 };");
1217 let defs = PackageDeclaration::from_typed_ast(&ta);
1218 let object_count = defs
1219 .shapes
1220 .iter()
1221 .filter(|s| matches!(s, crate::Shape::Object { .. }))
1222 .count();
1223 assert_eq!(
1224 object_count, 1,
1225 "expected one Object entry, got {object_count}"
1226 );
1227 }
1228
1229 #[test]
1230 fn distinguishes_object_shapes_with_different_fields() {
1231 let ta = typed_ast("let a = { x: 1 }; let b = { y: 2 };");
1232 let defs = PackageDeclaration::from_typed_ast(&ta);
1233 let object_count = defs
1234 .shapes
1235 .iter()
1236 .filter(|s| matches!(s, crate::Shape::Object { .. }))
1237 .count();
1238 assert_eq!(object_count, 2);
1239 }
1240
1241 #[test]
1242 fn canonical_shape_is_stable_across_modules() {
1243 let ta_a = typed_ast("let p = { x: 1, y: 2 };");
1244 let ta_b = typed_ast("let q = { y: 2, x: 1 };");
1245 let defs_a = PackageDeclaration::from_typed_ast(&ta_a);
1246 let defs_b = PackageDeclaration::from_typed_ast(&ta_b);
1247 let key_a = defs_a
1248 .shapes
1249 .iter()
1250 .find_map(|s| match s {
1251 crate::Shape::Object { .. } => Some(s.canonical_display()),
1252 _ => None,
1253 })
1254 .expect("object shape in defs_a");
1255 let key_b = defs_b
1256 .shapes
1257 .iter()
1258 .find_map(|s| match s {
1259 crate::Shape::Object { .. } => Some(s.canonical_display()),
1260 _ => None,
1261 })
1262 .expect("object shape in defs_b");
1263 assert_eq!(key_a, key_b, "canonical key should match across modules");
1264 }
1265
1266 #[test]
1267 fn nested_array_of_object_collects_both() {
1268 let ta = typed_ast("let xs: { x: number }[] = [];");
1269 let defs = PackageDeclaration::from_typed_ast(&ta);
1270 let has_array = defs
1271 .shapes
1272 .iter()
1273 .any(|s| matches!(s, crate::Shape::Array(_)));
1274 let has_object = defs
1275 .shapes
1276 .iter()
1277 .any(|s| matches!(s, crate::Shape::Object { .. }));
1278 assert!(has_array, "expected Array shape");
1279 assert!(has_object, "expected Object shape");
1280 }
1281}
1282
1283#[cfg(test)]
1284mod type_limit_checks {
1285 use std::collections::BTreeMap;
1286
1287 use super::*;
1288 use crate::compiler_limits::MAX_TYPE_DEPTH;
1289 use crate::type_size::TypeTooLarge;
1290
1291 fn nested(depth: u32) -> Type {
1292 (1..depth).fold(Type::Number, |inner, _| Type::Array(Box::new(inner)))
1293 }
1294
1295 fn namespace(name: &str) -> NamespaceSymbol {
1296 NamespaceSymbol {
1297 name: name.into(),
1298 mangled_prefix: crate::mangle::prelude(name),
1299 declaration_span: Span::at(crate::FileId(0)),
1300 values: BTreeMap::new(),
1301 types: BTreeMap::new(),
1302 namespaces: BTreeMap::new(),
1303 doc: None,
1304 }
1305 }
1306
1307 fn alias(ty: Type) -> TypeSymbol {
1308 TypeSymbol {
1309 name: "Deep".into(),
1310 mangled_name: crate::mangle::prelude("Deep"),
1311 declaration_span: Span::at(crate::FileId(0)),
1312 kind: TypeKind::Alias {
1313 generics: Vec::new(),
1314 ty,
1315 doc: None,
1316 },
1317 }
1318 }
1319
1320 #[test]
1321 fn namespace_cleanup_can_run_without_allocating_a_frontier() {
1322 let mut child = namespace("leaf");
1323 for _ in 0..1_000 {
1324 let mut parent = namespace("branch");
1325 parent.namespaces.insert("child".into(), child);
1326 parent
1327 .namespaces
1328 .insert("sibling".into(), namespace("sibling"));
1329 child = parent;
1330 }
1331 drop_namespace_children_without_allocation(&mut child.namespaces);
1332 assert!(child.namespaces.is_empty());
1333 }
1334
1335 #[test]
1336 fn a_type_in_a_nested_namespace_is_checked() {
1337 crate::type_size::tests::on_compiler_stack(|| {
1338 let mut inner = namespace("Inner");
1339 inner
1340 .types
1341 .insert("Deep".into(), alias(nested(MAX_TYPE_DEPTH + 1)));
1342 let mut outer = namespace("Outer");
1343 outer.namespaces.insert("Inner".into(), inner);
1344 let mut declaration = PackageDeclaration::with_package("dep");
1345 assert_eq!(declaration.check_type_limits(), Ok(()));
1346 declaration.namespaces.insert("Outer".into(), outer);
1347 assert_eq!(declaration.check_type_limits(), Err(TypeTooLarge::Depth));
1348 });
1349 }
1350
1351 #[test]
1352 fn runtime_declarations_are_checked() {
1353 crate::type_size::tests::on_compiler_stack(|| {
1354 let mut declaration = PackageDeclaration::with_package("dep");
1355 declaration.runtime_functions.insert(
1356 crate::mangle::prelude("f"),
1357 RuntimeFunction {
1358 params: vec![nested(MAX_TYPE_DEPTH)],
1359 ret: Type::Void,
1360 },
1361 );
1362 declaration
1363 .runtime_types
1364 .insert("Deep".into(), alias(nested(MAX_TYPE_DEPTH)));
1365 assert_eq!(declaration.check_type_limits(), Ok(()));
1366 declaration
1367 .runtime_globals
1368 .insert(crate::mangle::prelude("g"), nested(MAX_TYPE_DEPTH + 1));
1369 assert_eq!(declaration.check_type_limits(), Err(TypeTooLarge::Depth));
1370 });
1371 }
1372}
1373
1374#[cfg(test)]
1375mod non_finite_artifact_roundtrip {
1376 use super::{DefaultValue, EnumVariantValue, TypeKind};
1377 use crate::Type;
1378 use crate::types::LiteralF64;
1379
1380 #[test]
1383 fn non_finite_defaults_survive_json() {
1384 for value in [f64::INFINITY, f64::NEG_INFINITY, f64::NAN] {
1385 let json = serde_json::to_string(&DefaultValue::Number(value)).unwrap();
1386 assert!(
1387 !json.contains("null"),
1388 "non-finite default serialized to null: {json}"
1389 );
1390 let DefaultValue::Number(back) = serde_json::from_str(&json).unwrap() else {
1391 panic!("round-trip changed the variant: {json}");
1392 };
1393 assert_eq!(back.to_bits(), value.to_bits(), "round-trip lost {value}");
1394 }
1395 }
1396
1397 #[test]
1398 fn finite_defaults_still_serialize_as_numbers() {
1399 let json = serde_json::to_string(&DefaultValue::Number(1.5)).unwrap();
1400 assert_eq!(json, r#"{"Number":1.5}"#);
1401 let DefaultValue::Number(back) = serde_json::from_str(&json).unwrap() else {
1402 panic!("round-trip changed the variant");
1403 };
1404 assert_eq!(back, 1.5);
1405 }
1406
1407 #[test]
1408 fn non_finite_literal_types_survive_json() {
1409 let ty = Type::NumberLiteral(LiteralF64(f64::INFINITY));
1410 let json = serde_json::to_string(&ty).unwrap();
1411 assert_eq!(serde_json::from_str::<Type>(&json).unwrap(), ty);
1412 }
1413
1414 #[test]
1415 fn non_finite_enum_values_survive_json() {
1416 let value = EnumVariantValue::Number(f64::INFINITY);
1417 let json = serde_json::to_string(&value).unwrap();
1418 assert_eq!(
1419 serde_json::from_str::<EnumVariantValue>(&json).unwrap(),
1420 value
1421 );
1422 }
1423
1424 #[test]
1426 fn non_finite_enum_variants_survive_json() {
1427 let kind = TypeKind::NumberEnum {
1428 variants: vec![
1429 ("Huge".to_string(), f64::INFINITY),
1430 ("One".to_string(), 1.0),
1431 ],
1432 doc: None,
1433 };
1434 let json = serde_json::to_string(&kind).unwrap();
1435 assert!(
1436 json.contains(r#"["Huge","Infinity"]"#),
1437 "non-finite enum variant did not survive serialization: {json}"
1438 );
1439 let TypeKind::NumberEnum { variants, .. } = serde_json::from_str(&json).unwrap() else {
1440 panic!("round-trip changed the variant: {json}");
1441 };
1442 assert_eq!(variants[0].1.to_bits(), f64::INFINITY.to_bits());
1443 assert_eq!(variants[1].1, 1.0);
1444 }
1445}