1use std::collections::BTreeMap;
2
3use crate::arena::{self, ArenaError, ArenaKind};
4use crate::typechecker::infer::narrowing::{CastInfo, ReferencePath};
5use crate::{BinOp, BindingKind, ExprId, Ident, MangledName, Span, StmtId, Type, UnOp};
6
7#[derive(Clone, Debug, PartialEq)]
8pub struct TypedExpr {
9 pub kind: TypedExprKind,
10 pub span: Span,
11 pub ty: Type,
12}
13
14#[derive(Clone, Debug, PartialEq)]
15pub enum TypedExprKind {
16 Number(f64),
17 BigInt(String),
19 String(String),
20 Boolean(bool),
21 Null,
22 Undefined,
23 This,
28 Regex {
29 source: String,
30 flags: String,
31 },
32 LocalRef {
33 ident: Ident,
34 boxed: bool,
35 },
36 LocalNarrowRef {
40 binding: Ident,
41 path: crate::typechecker::infer::narrowing::ReferencePath,
42 },
43 GlobalRef {
44 mangled: MangledName,
45 name: Ident,
46 },
47 FunctionRef {
49 mangled: MangledName,
50 name: Ident,
51 },
52 EffectThen {
60 effect: ExprId,
61 result: ExprId,
62 },
63 Sequence {
67 stmts: Vec<StmtId>,
68 result: ExprId,
69 },
70 Binary {
71 op: BinOp,
72 lhs: ExprId,
73 rhs: ExprId,
74 },
75 Unary {
76 op: UnOp,
77 operand: ExprId,
78 },
79 Call {
81 mangled: MangledName,
82 args: Vec<ExprId>,
83 type_predicate: Option<Box<crate::TypePredicate>>,
85 },
86 CallClosure {
88 callee: ExprId,
89 args: Vec<ExprId>,
90 },
91 McpCall {
98 server: String,
99 tool: String,
100 args: Vec<ExprId>,
101 },
102 GenericCall {
106 mangled: MangledName,
107 type_args: Vec<Type>,
108 args: Vec<GenericArgument>,
109 return_cast: Option<crate::Type>,
112 type_predicate: Option<Box<crate::TypePredicate>>,
113 },
114 IntrinsicCall {
116 kind: Intrinsic,
117 args: Vec<ExprId>,
118 },
119 MethodCall {
122 receiver: ExprId,
123 iface: MangledName,
124 name: Ident,
125 args: Vec<ExprId>,
126 type_predicate: Option<Box<crate::TypePredicate>>,
127 },
128 SuperCtorCall {
132 parent: MangledName,
133 args: Vec<ExprId>,
134 },
135 SuperMethodCall {
139 owner: MangledName,
140 name: Ident,
141 args: Vec<ExprId>,
142 },
143 GenericMethodCall {
147 receiver: ExprId,
148 iface: MangledName,
149 name: Ident,
150 args: Vec<GenericArgument>,
151 return_cast: Option<crate::Type>,
152 type_predicate: Option<Box<crate::TypePredicate>>,
154 },
155 ObjectLiteral {
159 members: Vec<TypedObjectMember>,
164 fields: Vec<TypedObjectFieldOrigin>,
165 },
166 ArrayLiteral {
168 elements: Vec<TypedArrayElement>,
169 element_ty: Type,
170 },
171 TupleLiteral {
174 elements: Vec<ExprId>,
175 element_types: Vec<Type>,
176 },
177 FieldAccess {
178 receiver: ExprId,
179 name: Ident,
180 },
181 InterfacePropertyAccess {
183 receiver: ExprId,
184 iface: MangledName,
185 name: Ident,
186 },
187 NumberEnumMember {
190 enum_mangled: MangledName,
191 variant: Ident,
192 value: f64,
193 },
194 StringEnumMember {
196 enum_mangled: MangledName,
197 variant: Ident,
198 value: String,
199 },
200 IndexAccess {
201 receiver: ExprId,
202 index: ExprId,
203 },
204 Closure {
206 runtime_generics: Vec<String>,
207 params: Vec<TypedParam>,
208 return_type: Type,
209 body: ClosureBody,
210 captured: Vec<CapturedVar>,
211 },
212 TypeofTag {
215 value: ExprId,
216 tag: TypeofTagKind,
217 },
218 Narrowed {
220 path: ReferencePath,
221 source: ExprId,
222 binding: Ident,
223 cast_info: CastInfo,
224 inner: ExprId,
225 },
226 Ternary {
228 cond: ExprId,
229 then_: ExprId,
230 else_: ExprId,
231 },
232 NullishCoalesce {
234 lhs: ExprId,
235 rhs: ExprId,
236 },
237 OptionalChain {
240 base: ExprId,
241 parts: Vec<TypedChainPart>,
242 },
243 PostfixUnary {
246 op: crate::PostfixOp,
247 target: PostfixTarget,
248 },
249 NonNullAssert {
251 value: ExprId,
252 },
253 Cast {
258 value: ExprId,
259 target_ty: Type,
260 check: Option<Box<Type>>,
261 },
262 InstanceOf {
265 value: ExprId,
266 class: Type,
267 },
268}
269
270#[derive(Clone, Debug, PartialEq)]
271pub enum PostfixTarget {
272 Local {
274 ident: Ident,
275 boxed: bool,
276 target_ty: Type,
277 },
278 Global {
280 name: Ident,
281 mangled: MangledName,
282 target_ty: Type,
283 },
284 Field {
286 receiver: ExprId,
287 name: Ident,
288 target_ty: Type,
289 },
290 Index {
292 receiver: ExprId,
293 index: ExprId,
294 elem_ty: Type,
295 },
296}
297
298#[derive(Clone, Debug, PartialEq)]
300pub enum TypedChainPart {
301 Field {
302 name: Ident,
303 optional: bool,
304 result_ty: Type,
305 span: Span,
306 },
307 InterfaceProperty {
308 iface: MangledName,
309 name: Ident,
310 optional: bool,
311 result_ty: Type,
312 span: Span,
313 },
314 Index {
315 idx: ExprId,
316 optional: bool,
317 result_ty: Type,
318 span: Span,
319 },
320 Call {
321 args: Vec<ExprId>,
322 optional: bool,
323 result_ty: Type,
324 union_signature: Option<Type>,
328 span: Span,
329 },
330 MethodCall {
331 iface: MangledName,
332 name: Ident,
333 args: Vec<ExprId>,
334 optional: bool,
335 result_ty: Type,
336 span: Span,
337 },
338 NonNull { result_ty: Type, span: Span },
342}
343
344impl TypedChainPart {
345 pub fn result_ty(&self) -> &Type {
347 match self {
348 TypedChainPart::Field { result_ty, .. }
349 | TypedChainPart::InterfaceProperty { result_ty, .. }
350 | TypedChainPart::Index { result_ty, .. }
351 | TypedChainPart::Call { result_ty, .. }
352 | TypedChainPart::MethodCall { result_ty, .. }
353 | TypedChainPart::NonNull { result_ty, .. } => result_ty,
354 }
355 }
356
357 pub fn set_result_ty(&mut self, ty: Type) {
361 match self {
362 TypedChainPart::Field { result_ty, .. }
363 | TypedChainPart::InterfaceProperty { result_ty, .. }
364 | TypedChainPart::Index { result_ty, .. }
365 | TypedChainPart::Call { result_ty, .. }
366 | TypedChainPart::MethodCall { result_ty, .. }
367 | TypedChainPart::NonNull { result_ty, .. } => *result_ty = ty,
368 }
369 }
370
371 pub fn is_optional(&self) -> bool {
373 match self {
374 TypedChainPart::Field { optional, .. }
375 | TypedChainPart::InterfaceProperty { optional, .. }
376 | TypedChainPart::Index { optional, .. }
377 | TypedChainPart::Call { optional, .. }
378 | TypedChainPart::MethodCall { optional, .. } => *optional,
379 TypedChainPart::NonNull { .. } => false,
380 }
381 }
382}
383
384#[derive(Clone, Debug, PartialEq)]
386pub struct GenericArgument {
387 pub expr: ExprId,
388 pub is_generic: bool,
389}
390
391#[derive(Clone, Debug, PartialEq)]
394pub struct CapturedVar {
395 pub name: Ident,
396 pub ty: Type,
397 pub boxed: bool,
398}
399
400#[derive(Clone, Debug, PartialEq)]
401pub enum ClosureBody {
402 Expr(ExprId),
403 Block(StmtId),
404}
405
406#[derive(Clone, Debug, PartialEq)]
407pub struct TypedObjectLiteralField {
408 pub name: Ident,
409 pub value: ExprId,
410}
411
412#[derive(Clone, Debug, PartialEq)]
413pub struct TypedObjectFieldOrigin {
414 pub name: Ident,
415 pub source: TypedObjectFieldSource,
416 pub optional: bool,
420 pub ty: Type,
426}
427
428pub fn object_literal_layout(ty: &Type, fields: &[TypedObjectFieldOrigin]) -> Type {
433 if let Type::Object { index: Some(_), .. } = ty {
434 return ty.clone();
435 }
436 let fields = fields
437 .iter()
438 .map(|field| {
439 (
440 field.name.name.clone(),
441 crate::ObjectField {
442 ty: field.ty.clone(),
443 optional: field.optional,
444 readonly: false,
445 method: false,
446 },
447 )
448 })
449 .collect();
450 Type::Object {
451 index: None,
452 fields,
453 }
454}
455
456#[derive(Clone, Debug, PartialEq)]
457pub enum TypedObjectFieldSource {
458 Literal(ExprId),
459 Absent(ExprId),
460 Spread {
463 source_index: usize,
464 field_name: String,
465 source_ty: Type,
466 fallback: Option<Box<TypedObjectFieldSource>>,
470 },
471}
472
473impl TypedObjectFieldSource {
474 pub fn literal_expr_id(&self) -> Option<ExprId> {
475 match self {
476 TypedObjectFieldSource::Literal(id) | TypedObjectFieldSource::Absent(id) => Some(*id),
477 TypedObjectFieldSource::Spread { .. } => None,
478 }
479 }
480}
481
482#[derive(Clone, Copy, Debug, PartialEq)]
484pub enum TypedObjectMember {
485 Computed {
486 key: ExprId,
487 value: ExprId,
488 },
489 Value(ExprId),
491 Spread {
496 source: ExprId,
497 by_name: bool,
498 },
499}
500
501impl TypedObjectMember {
502 pub fn expressions(self) -> impl Iterator<Item = ExprId> {
503 let key = match self {
504 Self::Computed { key, .. } => Some(key),
505 _ => None,
506 };
507 key.into_iter().chain(std::iter::once(self.expr_id()))
508 }
509
510 pub fn expr_id(self) -> ExprId {
511 match self {
512 TypedObjectMember::Value(id)
513 | TypedObjectMember::Spread { source: id, .. }
514 | TypedObjectMember::Computed { value: id, .. } => id,
515 }
516 }
517}
518
519#[derive(Clone, Debug, PartialEq)]
520pub enum TypedArrayElement {
521 Value(ExprId),
522 Spread(ExprId),
523}
524
525impl TypedArrayElement {
526 pub fn expr_id(&self) -> ExprId {
527 match self {
528 TypedArrayElement::Value(id) | TypedArrayElement::Spread(id) => *id,
529 }
530 }
531}
532
533#[derive(Copy, Clone, Debug, PartialEq, Eq)]
534pub enum TypeofTagKind {
535 Undefined,
536 BigInt,
537 Number,
538 String,
539 Boolean,
540 Object,
542 Function,
544}
545
546#[derive(Copy, Clone, Debug, PartialEq, Eq)]
547pub enum Intrinsic {
548 Assert,
550 JsonStringify,
552 JsonParse,
554 BigIntFromString,
556 ToString,
559}
560
561impl Intrinsic {
562 pub fn from_name(name: &str) -> Option<Self> {
563 match name {
564 "assert" => Some(Intrinsic::Assert),
565 _ => None,
566 }
567 }
568
569 pub fn params(self) -> Vec<crate::Param> {
572 use crate::{Param, Type};
573 match self {
574 Intrinsic::Assert => vec![
575 Param::new("condition", Type::Boolean),
576 Param {
577 name: "message".to_string(),
578 optional: true,
579 ty: Type::String,
580 default: Some(crate::DefaultValue::String("assertion failed".to_string())),
581 rest: false,
582 },
583 ],
584 Intrinsic::BigIntFromString => vec![Param::new("value", Type::String)],
585 Intrinsic::JsonStringify => vec![Param::new("value", Type::Error)],
586 Intrinsic::JsonParse => vec![Param::new("text", Type::String)],
587 Intrinsic::ToString => vec![Param::new("value", Type::Unknown)],
588 }
589 }
590
591 pub fn ret(self) -> crate::Type {
593 match self {
594 Intrinsic::Assert => crate::Type::Void,
595 Intrinsic::BigIntFromString => crate::Type::BigInt,
596 Intrinsic::JsonStringify => crate::Type::String,
597 Intrinsic::JsonParse => crate::Type::Unknown,
598 Intrinsic::ToString => crate::Type::String,
599 }
600 }
601
602 pub fn name(self) -> &'static str {
603 match self {
604 Intrinsic::Assert => "assert",
605 Intrinsic::BigIntFromString => "BigInt.fromString",
606 Intrinsic::JsonStringify => "JSON.stringify",
607 Intrinsic::JsonParse => "JSON.parse",
608 Intrinsic::ToString => "String",
609 }
610 }
611}
612
613#[derive(Clone, Debug, PartialEq)]
614pub struct TypedStmt {
615 pub kind: TypedStmtKind,
616 pub span: Span,
617}
618
619#[derive(Clone, Copy, Debug, PartialEq, Eq)]
622pub enum ForOfKind {
623 Array,
624 Iterator,
625 Iterable,
626}
627
628#[derive(Clone, Debug, PartialEq)]
629pub enum TypedStmtKind {
630 Let {
633 name: Ident,
634 ty: Type,
635 value: ExprId,
636 boxed: bool,
637 doc: Option<crate::DocComment>,
638 },
639 ReboxLocal {
649 ident: Ident,
650 ty: Type,
651 },
652 Const {
655 name: Ident,
656 ty: Type,
657 value: ExprId,
658 doc: Option<crate::DocComment>,
659 },
660 If {
661 condition: ExprId,
662 then_block: StmtId,
663 else_block: Option<StmtId>,
664 },
665 While {
666 condition: ExprId,
667 body: StmtId,
668 },
669 For {
671 init: Option<StmtId>,
672 condition: Option<ExprId>,
673 update: Option<StmtId>,
674 body: StmtId,
675 },
676 ForOf {
679 binding_kind: BindingKind,
680 name: Ident,
681 element_ty: Type,
682 iter: ExprId,
683 body: StmtId,
684 kind: ForOfKind,
685 },
686 DoWhile {
689 body: StmtId,
690 condition: ExprId,
691 },
692 Switch {
696 discriminant: ExprId,
697 discriminant_ty: Type,
698 cases: Vec<TypedSwitchCase>,
699 default: Option<StmtId>,
700 },
701 Break,
702 Continue,
706 Return(Option<ExprId>),
707 Throw {
709 value: ExprId,
710 },
711 Try {
715 body: StmtId,
716 catches: Vec<TypedCatchClause>,
717 finally: Option<StmtId>,
718 },
719 Expr(ExprId),
720 Block(Vec<StmtId>),
721 AssignLocal {
724 ident: Ident,
725 target_ty: Type,
726 value: ExprId,
727 boxed: bool,
728 narrowed_shadow_ty: Option<Type>,
732 },
733 AssignGlobal {
735 ident: Ident,
736 mangled: MangledName,
737 target_ty: Type,
738 value: ExprId,
739 },
740 AssignField {
741 receiver: ExprId,
742 name: Ident,
743 value: ExprId,
744 },
745 AssignIndex {
748 receiver: ExprId,
749 index: ExprId,
750 value: ExprId,
751 elem_ty: Type,
752 },
753 NarrowRegion {
756 path: ReferencePath,
757 source: ExprId,
758 binding: Ident,
759 cast_info: CastInfo,
760 body: StmtId,
761 },
762}
763
764#[derive(Clone, Debug, PartialEq)]
768pub struct TypedCatchClause {
769 pub binding: Ident,
770 pub ty: Type,
771 pub body: StmtId,
772 pub boxed: bool,
773 pub span: Span,
774}
775
776#[derive(Clone, Debug, PartialEq)]
779pub struct TypedSwitchCase {
780 pub values: Vec<TypedSwitchValue>,
781 pub body: StmtId,
782 pub span: Span,
783}
784
785impl TypedSwitchCase {
786 pub fn label_comparisons(&self) -> impl Iterator<Item = ExprId> + '_ {
789 self.values.iter().filter_map(|value| match value {
790 TypedSwitchValue::Expr { comparison, .. } => Some(*comparison),
791 _ => None,
792 })
793 }
794}
795
796#[derive(Clone, Debug, PartialEq)]
798pub enum TypedSwitchValue {
799 Expr {
802 label: ExprId,
803 comparison: ExprId,
806 literal: Option<crate::typechecker::infer::narrowing::LiteralValue>,
809 span: Span,
810 },
811 String {
812 value: String,
813 span: Span,
814 },
815 Number {
816 value: f64,
817 span: Span,
818 },
819 Boolean {
820 value: bool,
821 span: Span,
822 },
823 Null {
824 span: Span,
825 },
826 Undefined {
827 span: Span,
828 },
829 Enum {
831 enum_name: MangledName,
832 member: Ident,
833 value: EnumVariantPayload,
834 span: Span,
835 },
836}
837
838#[derive(Clone, Debug, PartialEq)]
840pub enum EnumVariantPayload {
841 Number(f64),
842 String(String),
843}
844
845#[derive(Clone, Debug, PartialEq)]
846pub struct TypedParam {
847 pub name: Ident,
848 pub optional: bool,
850 pub ty: Type,
851 pub boxed: bool,
855 pub rest: bool,
857 pub default: Option<crate::DefaultValue>,
860}
861
862#[derive(Default, Clone, Debug)]
863pub struct TypedAst {
864 pub parameter_default_prologues: std::collections::BTreeMap<StmtId, usize>,
866 pub closure_this: std::collections::BTreeMap<ExprId, Type>,
868 pub closure_names: std::collections::BTreeMap<ExprId, Ident>,
870 pub nested_function_names: std::collections::BTreeMap<ExprId, Ident>,
873 pub placeholder_closures: std::collections::BTreeSet<ExprId>,
877 pub authored_arguments: std::collections::BTreeMap<(u32, u32, u32), Vec<ExprId>>,
879 pub authored_parameter_types: std::collections::BTreeMap<(u32, u32, u32), Type>,
881 pub parameter_inputs: std::collections::BTreeSet<ExprId>,
883 pub parameter_initializations: std::collections::BTreeSet<StmtId>,
885 pub runtime_source_types: std::collections::BTreeMap<ExprId, Type>,
888 pub runtime_chain_types: std::collections::BTreeMap<ExprId, Vec<Type>>,
889 pub spread_mask_fields:
893 std::collections::BTreeMap<ExprId, std::collections::BTreeMap<String, Type>>,
894 pub spread_omitted_fields:
897 std::collections::BTreeMap<ExprId, std::collections::BTreeSet<String>>,
898 pub exhaustive_switches: std::collections::BTreeSet<ExprId>,
901 pub package_name: String,
903 exprs: Vec<TypedExpr>,
904 stmts: Vec<TypedStmt>,
905 pub globals: Vec<TypedGlobal>,
907 pub rebindable_globals: std::collections::BTreeMap<MangledName, String>,
913 pub functions: Vec<TypedFunction>,
915 pub top_level_statements: Vec<StmtId>,
917 pub class_declaration_points: std::collections::BTreeMap<MangledName, usize>,
920 pub types: Vec<TypedTypeDecl>,
924 pub shapes: Vec<crate::Shape>,
927 pub exports: Vec<ExportEntry>,
931 pub imported_packages: std::collections::BTreeSet<String>,
935 pub runtime_type_tests: std::collections::BTreeMap<Type, FieldNarrowingTest>,
939 pub runtime_class_fields: std::collections::BTreeMap<Type, Type>,
941 pub runtime_class_parameters: std::collections::BTreeMap<MangledName, Vec<String>>,
942 pub runtime_class_contexts: std::collections::BTreeMap<Type, Vec<InstanceTypeContext>>,
943 pub runtime_field_guards: std::collections::BTreeMap<Type, Vec<InstantiatedFieldGuard>>,
944}
945
946#[derive(Clone, Debug)]
947pub struct InstanceTypeContext {
948 pub declaration: MangledName,
949 pub args: Vec<Type>,
950}
951
952#[derive(Clone, Debug, PartialEq)]
955pub struct ExportEntry {
956 pub public_name: MangledName,
957 pub target: MangledName,
958 pub kind: ExportKind,
959 pub span: Span,
961}
962
963#[derive(Clone, Copy, Debug, PartialEq, Eq)]
964pub enum ExportKind {
965 Function,
966 Global,
967 Type,
968}
969
970#[derive(Clone, Debug, PartialEq)]
971pub struct TypedGlobal {
972 pub name: Ident,
973 pub mangled_name: MangledName,
974 pub ty: Type,
975 pub kind: GlobalKind,
976 pub doc: Option<crate::DocComment>,
977 pub span: Span,
978}
979
980#[derive(Copy, Clone, Debug, PartialEq, Eq)]
982pub enum GlobalKind {
983 Let,
984 Const,
985}
986
987#[derive(Clone, Debug, PartialEq)]
988pub struct TypedFunction {
989 pub name: Ident,
990 pub mangled_name: MangledName,
991 pub generics: Vec<String>,
994 pub params: Vec<TypedParam>,
995 pub return_type: Type,
996 pub type_predicate: Option<crate::TypePredicate>,
998 pub body: StmtId,
999 pub doc: Option<crate::DocComment>,
1000 pub span: Span,
1001}
1002
1003#[derive(Clone, Debug, PartialEq)]
1005pub enum TypedTypeDecl {
1006 Interface(TypedInterfaceDecl),
1007 Class(TypedClassDecl),
1008 NumberEnum(TypedNumberEnumDecl),
1009 StringEnum(TypedStringEnumDecl),
1010 Alias(TypedTypeAliasDecl),
1011}
1012
1013#[derive(Clone, Debug, PartialEq)]
1016pub struct TypedClassDecl {
1017 pub name: Ident,
1018 pub fields: Vec<TypedClassField>,
1019 pub static_methods: BTreeMap<String, crate::Visibility>,
1022 pub static_fields: BTreeMap<String, crate::FieldSig>,
1025 pub constructor: Option<TypedClassConstructor>,
1026 pub inherited_ctor_params: Vec<TypedParam>,
1035 pub methods: Vec<TypedClassMethod>,
1036 pub accessors: Vec<TypedClassAccessor>,
1041 pub extends: Option<crate::MangledName>,
1042 pub implements: Vec<crate::MangledName>,
1043 pub mangled_name: crate::MangledName,
1044 pub doc: Option<crate::DocComment>,
1045}
1046
1047#[derive(Clone, Debug, PartialEq)]
1050pub enum TypedClassAccessor {
1051 Getter {
1052 name: Ident,
1053 ret_ty: Type,
1054 visibility: crate::Visibility,
1055 body: StmtId,
1056 },
1057 Setter {
1058 name: Ident,
1059 param: TypedParam,
1061 visibility: crate::Visibility,
1062 body: StmtId,
1063 },
1064}
1065
1066impl TypedClassAccessor {
1067 pub fn name(&self) -> &Ident {
1068 match self {
1069 TypedClassAccessor::Getter { name, .. } | TypedClassAccessor::Setter { name, .. } => {
1070 name
1071 }
1072 }
1073 }
1074
1075 pub fn body(&self) -> StmtId {
1076 match self {
1077 TypedClassAccessor::Getter { body, .. } | TypedClassAccessor::Setter { body, .. } => {
1078 *body
1079 }
1080 }
1081 }
1082
1083 pub fn visibility(&self) -> crate::Visibility {
1084 match self {
1085 TypedClassAccessor::Getter { visibility, .. }
1086 | TypedClassAccessor::Setter { visibility, .. } => *visibility,
1087 }
1088 }
1089}
1090
1091#[derive(Clone, Debug, PartialEq)]
1092pub struct TypedClassField {
1093 pub name: Ident,
1094 pub ty: Type,
1095 pub visibility: crate::Visibility,
1096 pub readonly: bool,
1097 pub optional: bool,
1098 pub initializer: Option<ExprId>,
1099 pub auto_assigned: bool,
1103 pub narrowing_check: Option<Box<FieldNarrowingCheck>>,
1107 pub doc: Option<crate::DocComment>,
1108}
1109
1110#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1122pub struct FieldNarrowingCheck {
1123 #[serde(default)]
1124 pub declaration: Option<MangledName>,
1125 pub test: FieldNarrowingTest,
1126 #[serde(default)]
1129 pub minimal_test_target: Option<Type>,
1130 pub message: String,
1131}
1132
1133#[derive(Clone, Debug, PartialEq)]
1134pub struct InstantiatedFieldGuard {
1135 pub field: String,
1136 pub target: Type,
1137 pub check: FieldNarrowingCheck,
1138}
1139
1140#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1144pub enum FieldNarrowingTest {
1145 NonNull,
1157 Shape(Type),
1160 Interface(InterfaceNarrowingTest),
1165 Substituted,
1169 Representation,
1172}
1173
1174#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1175pub struct InterfaceNarrowingTest {
1176 pub index: Option<crate::IndexSignature>,
1177 pub members: std::collections::BTreeMap<String, crate::ObjectField>,
1178 pub methods: std::collections::BTreeSet<String>,
1179 pub non_shape_carriers: std::collections::BTreeSet<InterfaceCarrier>,
1180 #[serde(default = "interface_shape_allowed_default")]
1184 pub shape_allowed: bool,
1185 pub nullable: bool,
1186 #[serde(default)]
1187 pub undefined: bool,
1188}
1189
1190fn interface_shape_allowed_default() -> bool {
1191 true
1192}
1193
1194#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
1195pub enum InterfaceCarrier {
1196 Number,
1197 Boolean,
1198 String,
1199 BigInt,
1200 Uint8Array,
1201 ArrayAny,
1202 Array(Type),
1203 MapAny,
1204 Map(Type, Type),
1205 SetAny,
1206 Set(Type),
1207 RegExp,
1208 RegExpMatch,
1209 TemporalInstant,
1210 TemporalDuration,
1211 TemporalZonedDateTime,
1212 TemporalPlainDate,
1213 TemporalPlainTime,
1214 TemporalPlainDateTime,
1215 TemporalPlainYearMonth,
1216 TemporalPlainMonthDay,
1217 ObjectShape,
1218 Url,
1219 FsStat,
1220 FsPeek,
1221 FsDirEntry,
1222 FsInfo,
1223 FsMountInfo,
1224 FsFileWriter,
1225 HttpResponse,
1226 HttpDownloadResult,
1227 SessionEntry,
1228 SessionPage,
1229}
1230
1231pub(crate) fn runtime_type_is_testable(ty: &Type) -> bool {
1235 runtime_type_is_testable_inner(ty, false)
1236}
1237
1238pub(crate) fn field_runtime_type_is_testable(ty: &Type) -> bool {
1243 runtime_type_is_testable_inner(ty, true)
1244}
1245
1246fn runtime_type_is_testable_inner(ty: &Type, allow_recursive_ref: bool) -> bool {
1247 match ty.peel() {
1248 Type::Null
1249 | Type::Undefined
1250 | Type::Void
1251 | Type::Number
1252 | Type::NumberLiteral(_)
1253 | Type::Boolean
1254 | Type::BooleanLiteral(_)
1255 | Type::String
1256 | Type::StringLiteral(_)
1257 | Type::BigInt
1258 | Type::Uint8Array
1259 | Type::Unknown
1260 | Type::Function { .. }
1261 | Type::NumberEnum { .. }
1262 | Type::StringEnum { .. }
1263 | Type::ClassRef { .. } => true,
1264 Type::AliasRef { .. } | Type::InterfaceRef { .. } => allow_recursive_ref,
1265 Type::Array(elem) => runtime_type_is_testable_inner(elem, allow_recursive_ref),
1266 Type::Tuple(elems) => elems
1267 .iter()
1268 .all(|elem| runtime_type_is_testable_inner(elem, allow_recursive_ref)),
1269 Type::Union(elems) => elems
1270 .iter()
1271 .all(|elem| runtime_type_is_testable_inner(elem, allow_recursive_ref)),
1272 Type::Object { fields, index } => {
1273 index
1274 .as_ref()
1275 .is_none_or(|i| runtime_type_is_testable_inner(&i.value, allow_recursive_ref))
1276 && fields
1277 .values()
1278 .all(|field| runtime_type_is_testable_inner(&field.ty, allow_recursive_ref))
1279 }
1280 _ => false,
1281 }
1282}
1283
1284impl TypedClassDecl {
1285 pub fn effective_ctor_params(&self) -> &[TypedParam] {
1290 match &self.constructor {
1291 Some(c) => &c.params,
1292 None => &self.inherited_ctor_params,
1293 }
1294 }
1295}
1296
1297#[derive(Clone, Debug, PartialEq)]
1298pub struct TypedClassConstructor {
1299 pub doc: Option<Box<crate::DocComment>>,
1300 pub span: Span,
1301 pub params: Vec<TypedParam>,
1302 pub body: StmtId,
1303}
1304
1305#[derive(Clone, Debug, PartialEq)]
1306pub struct TypedClassMethod {
1307 pub name: Ident,
1308 pub generics: Vec<String>,
1309 pub params: Vec<TypedParam>,
1310 pub return_type: Type,
1311 pub body: StmtId,
1312 pub visibility: crate::Visibility,
1313 pub doc: Option<crate::DocComment>,
1314}
1315
1316#[derive(Clone, Debug, PartialEq)]
1317pub struct TypedInterfaceDecl {
1318 pub property_names: std::collections::BTreeSet<String>,
1320 pub index: Option<crate::IndexSignature>,
1321 pub name: Ident,
1322 pub generics: Vec<String>,
1323 pub members: Vec<TypedInterfaceMember>,
1324 pub doc: Option<crate::DocComment>,
1325}
1326
1327#[derive(Clone, Debug, PartialEq)]
1328pub enum TypedInterfaceMember {
1329 Method {
1330 name: Ident,
1331 generics: Vec<String>,
1332 params: Vec<TypedParam>,
1333 return_type: Type,
1334 doc: Option<crate::DocComment>,
1335 },
1336 Property {
1337 name: Ident,
1338 ty: Type,
1339 readonly: bool,
1340 optional: bool,
1342 doc: Option<crate::DocComment>,
1343 },
1344}
1345
1346#[derive(Clone, Debug, PartialEq)]
1347pub struct TypedNumberEnumDecl {
1348 pub name: Ident,
1349 pub members: Vec<TypedNumberEnumMember>,
1350 pub doc: Option<crate::DocComment>,
1351}
1352
1353#[derive(Clone, Debug, PartialEq)]
1354pub struct TypedNumberEnumMember {
1355 pub name: Ident,
1356 pub value: f64,
1357 pub doc: Option<crate::DocComment>,
1358}
1359
1360#[derive(Clone, Debug, PartialEq)]
1361pub struct TypedStringEnumDecl {
1362 pub name: Ident,
1363 pub members: Vec<TypedStringEnumMember>,
1364 pub doc: Option<crate::DocComment>,
1365}
1366
1367#[derive(Clone, Debug, PartialEq)]
1368pub struct TypedStringEnumMember {
1369 pub name: Ident,
1370 pub value: String,
1371 pub doc: Option<crate::DocComment>,
1372}
1373
1374#[derive(Clone, Debug, PartialEq)]
1376pub struct TypedTypeAliasDecl {
1377 pub name: Ident,
1378 pub generics: Vec<String>,
1379 pub ty: Type,
1380 pub doc: Option<crate::DocComment>,
1381}
1382
1383impl TypedAst {
1384 pub(crate) fn has_string_index(&self, ty: &Type) -> bool {
1385 match ty.peel() {
1386 Type::Object { index, .. } => index.is_some(),
1387 Type::Union(members) => members.iter().any(|member| self.has_string_index(member)),
1388 Type::InterfaceRef { .. } => matches!(
1389 self.runtime_type_tests.get(ty.peel()),
1390 Some(FieldNarrowingTest::Interface(interface)) if interface.index.is_some()
1391 ),
1392 _ => false,
1393 }
1394 }
1395
1396 pub fn new() -> Self {
1397 Self {
1398 package_name: crate::mangle::USER_PACKAGE.to_string(),
1399 ..Self::default()
1400 }
1401 }
1402
1403 pub fn with_package(package_name: impl Into<String>) -> Self {
1404 Self {
1405 package_name: package_name.into(),
1406 ..Self::default()
1407 }
1408 }
1409
1410 pub fn record_authored_arguments(&mut self, span: Span, args: Vec<ExprId>) {
1411 self.authored_arguments
1412 .insert((span.file.0, span.start, span.end), args);
1413 }
1414
1415 pub fn authored_call_arguments(&self, span: Span) -> Option<&Vec<ExprId>> {
1416 self.authored_arguments
1417 .get(&(span.file.0, span.start, span.end))
1418 }
1419
1420 pub fn try_push_expr(&mut self, expr: TypedExpr) -> Result<ExprId, ArenaError> {
1422 arena::push(&mut self.exprs, expr, ArenaKind::TypedExpressions).map(ExprId)
1423 }
1424
1425 pub fn try_push_stmt(&mut self, stmt: TypedStmt) -> Result<StmtId, ArenaError> {
1427 arena::push(&mut self.stmts, stmt, ArenaKind::TypedStatements).map(StmtId)
1428 }
1429
1430 pub fn try_expr(&self, id: ExprId) -> Result<&TypedExpr, ArenaError> {
1431 arena::get(&self.exprs, id.0, ArenaKind::TypedExpressions)
1432 }
1433
1434 pub fn try_stmt(&self, id: StmtId) -> Result<&TypedStmt, ArenaError> {
1435 arena::get(&self.stmts, id.0, ArenaKind::TypedStatements)
1436 }
1437
1438 pub fn try_expr_mut(&mut self, id: ExprId) -> Result<&mut TypedExpr, ArenaError> {
1439 arena::get_mut(&mut self.exprs, id.0, ArenaKind::TypedExpressions)
1440 }
1441
1442 pub fn try_stmt_mut(&mut self, id: StmtId) -> Result<&mut TypedStmt, ArenaError> {
1443 arena::get_mut(&mut self.stmts, id.0, ArenaKind::TypedStatements)
1444 }
1445
1446 pub fn expr_ids(&self) -> Result<impl DoubleEndedIterator<Item = ExprId> + use<>, ArenaError> {
1448 Ok(arena::ids(self.exprs.len(), ArenaKind::TypedExpressions)?.map(ExprId))
1449 }
1450
1451 pub fn stmt_ids(&self) -> Result<impl DoubleEndedIterator<Item = StmtId> + use<>, ArenaError> {
1453 Ok(arena::ids(self.stmts.len(), ArenaKind::TypedStatements)?.map(StmtId))
1454 }
1455
1456 pub fn class_body_roots(&self) -> Vec<StmtId> {
1460 let mut roots = Vec::new();
1461 for decl in &self.types {
1462 if let TypedTypeDecl::Class(c) = decl {
1463 if let Some(ctor) = &c.constructor {
1464 roots.push(ctor.body);
1465 }
1466 roots.extend(c.methods.iter().map(|m| m.body));
1467 roots.extend(c.accessors.iter().map(TypedClassAccessor::body));
1468 }
1469 }
1470 roots
1471 }
1472
1473 pub fn class_field_initializers(&self) -> Vec<ExprId> {
1476 let mut inits = Vec::new();
1477 for decl in &self.types {
1478 if let TypedTypeDecl::Class(c) = decl {
1479 inits.extend(c.fields.iter().filter_map(|f| f.initializer));
1480 }
1481 }
1482 inits
1483 }
1484
1485 pub fn source_type(&self, id: ExprId) -> Result<&Type, ArenaError> {
1486 let expr = self.try_expr(id)?;
1487 Ok(self.runtime_source_types.get(&id).unwrap_or(&expr.ty))
1488 }
1489
1490 pub fn exprs_len(&self) -> usize {
1492 self.exprs.len()
1493 }
1494
1495 pub fn stmts_len(&self) -> usize {
1496 self.stmts.len()
1497 }
1498
1499 pub fn is_effect_free(&self, id: ExprId) -> Result<bool, ArenaError> {
1505 Ok(matches!(
1506 self.try_expr(id)?.kind,
1507 TypedExprKind::Number(_)
1508 | TypedExprKind::BigInt(_)
1509 | TypedExprKind::String(_)
1510 | TypedExprKind::Boolean(_)
1511 | TypedExprKind::Null
1512 | TypedExprKind::Undefined
1513 | TypedExprKind::This
1514 | TypedExprKind::Regex { .. }
1515 | TypedExprKind::LocalRef { .. }
1516 | TypedExprKind::LocalNarrowRef { .. }
1517 | TypedExprKind::GlobalRef { .. }
1518 | TypedExprKind::FunctionRef { .. }
1519 | TypedExprKind::NumberEnumMember { .. }
1520 | TypedExprKind::StringEnumMember { .. }
1521 ))
1522 }
1523}
1524
1525#[cfg(test)]
1526mod tests {
1527 use super::{TypedAst, TypedExpr, TypedExprKind, TypedParam, TypedStmt, TypedStmtKind};
1528 use crate::{BinOp, Ident, Span, Type, UnOp};
1529
1530 #[test]
1531 fn arena_round_trip_for_typed_exprs() {
1532 let mut ast = TypedAst::new();
1533 let id = ast
1534 .try_push_expr(TypedExpr {
1535 kind: TypedExprKind::Number(42.0),
1536 span: Span::new(crate::FileId(0), 0, 2).unwrap(),
1537 ty: Type::Number,
1538 })
1539 .unwrap();
1540 assert_eq!(id.0, 0);
1541 let e = ast.try_expr(id).unwrap();
1542 assert_eq!(e.kind, TypedExprKind::Number(42.0));
1543 assert_eq!(e.span, Span::new(crate::FileId(0), 0, 2).unwrap());
1544 assert_eq!(e.ty, Type::Number);
1545 }
1546
1547 #[test]
1548 fn arena_round_trip_for_typed_stmts() {
1549 let mut ast = TypedAst::new();
1550 let expr_id = ast
1551 .try_push_expr(TypedExpr {
1552 kind: TypedExprKind::Boolean(true),
1553 span: Span::new(crate::FileId(0), 0, 4).unwrap(),
1554 ty: Type::Boolean,
1555 })
1556 .unwrap();
1557 let stmt_id = ast
1558 .try_push_stmt(TypedStmt {
1559 kind: TypedStmtKind::Expr(expr_id),
1560 span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1561 })
1562 .unwrap();
1563 assert_eq!(stmt_id.0, 0);
1564 let s = ast.try_stmt(stmt_id).unwrap();
1565 assert_eq!(s.span, Span::new(crate::FileId(0), 0, 5).unwrap());
1566 assert!(matches!(s.kind, TypedStmtKind::Expr(_)));
1567 }
1568
1569 #[test]
1570 fn build_typed_binary_tree() {
1571 let mut ast = TypedAst::new();
1572 let lhs = ast
1573 .try_push_expr(TypedExpr {
1574 kind: TypedExprKind::Number(1.0),
1575 span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1576 ty: Type::Number,
1577 })
1578 .unwrap();
1579 let rhs = ast
1580 .try_push_expr(TypedExpr {
1581 kind: TypedExprKind::Number(2.0),
1582 span: Span::new(crate::FileId(0), 4, 5).unwrap(),
1583 ty: Type::Number,
1584 })
1585 .unwrap();
1586 let sum = ast
1587 .try_push_expr(TypedExpr {
1588 kind: TypedExprKind::Binary {
1589 op: BinOp::Add,
1590 lhs,
1591 rhs,
1592 },
1593 span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1594 ty: Type::Number,
1595 })
1596 .unwrap();
1597
1598 let outer = ast.try_expr(sum).unwrap();
1599 assert_eq!(outer.ty, Type::Number);
1600 let TypedExprKind::Binary { op, lhs, rhs } = outer.kind else {
1601 panic!("expected Binary");
1602 };
1603 assert_eq!(op, BinOp::Add);
1604 assert_eq!(ast.try_expr(lhs).unwrap().ty, Type::Number);
1605 assert_eq!(ast.try_expr(rhs).unwrap().ty, Type::Number);
1606 }
1607
1608 #[test]
1609 fn build_typed_function_with_return() {
1610 let mut ast = TypedAst::new();
1611 let one = ast
1612 .try_push_expr(TypedExpr {
1613 kind: TypedExprKind::Number(1.0),
1614 span: Span::new(crate::FileId(0), 31, 32).unwrap(),
1615 ty: Type::Number,
1616 })
1617 .unwrap();
1618 let ret = ast
1619 .try_push_stmt(TypedStmt {
1620 kind: TypedStmtKind::Return(Some(one)),
1621 span: Span::new(crate::FileId(0), 24, 33).unwrap(),
1622 })
1623 .unwrap();
1624 let body = ast
1625 .try_push_stmt(TypedStmt {
1626 kind: TypedStmtKind::Block(vec![ret]),
1627 span: Span::new(crate::FileId(0), 22, 35).unwrap(),
1628 })
1629 .unwrap();
1630 ast.functions.push(crate::TypedFunction {
1631 name: Ident {
1632 name: "f".to_string(),
1633 span: Span::new(crate::FileId(0), 9, 10).unwrap(),
1634 },
1635 mangled_name: crate::mangle::package_symbol("main", "f"),
1636 generics: vec![],
1637 params: vec![],
1638 return_type: Type::Number,
1639 type_predicate: None,
1640 body,
1641 doc: None,
1642 span: Span::new(crate::FileId(0), 0, 35).unwrap(),
1643 });
1644
1645 let f = &ast.functions[0];
1646 assert_eq!(f.return_type, Type::Number);
1647 assert!(matches!(
1648 ast.try_stmt(f.body).unwrap().kind,
1649 TypedStmtKind::Block(_)
1650 ));
1651 }
1652
1653 #[test]
1654 fn clone_and_equality() {
1655 let kind = TypedExprKind::Unary {
1656 op: UnOp::Neg,
1657 operand: crate::ExprId(0),
1658 };
1659 assert_eq!(kind.clone(), kind);
1660 }
1661
1662 #[test]
1663 fn typed_param_construction() {
1664 let p = TypedParam {
1665 optional: false,
1666 name: Ident {
1667 name: "param".to_string(),
1668 span: Span::new(crate::FileId(0), 10, 15).unwrap(),
1669 },
1670 ty: Type::String,
1671 boxed: false,
1672 rest: false,
1673 default: None,
1674 };
1675 assert_eq!(p.name.name, "param");
1676 assert_eq!(p.name.span, Span::new(crate::FileId(0), 10, 15).unwrap());
1677 assert_eq!(p.ty, Type::String);
1678 assert!(!p.boxed);
1679 }
1680
1681 #[test]
1682 fn local_ref_carries_name_and_boxed_flag() {
1683 let mut ast = TypedAst::new();
1684 let id = ast
1685 .try_push_expr(TypedExpr {
1686 kind: TypedExprKind::LocalRef {
1687 ident: Ident {
1688 name: "x".to_string(),
1689 span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1690 },
1691 boxed: false,
1692 },
1693 span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1694 ty: Type::Number,
1695 })
1696 .unwrap();
1697 match ast.try_expr(id).unwrap().kind {
1698 TypedExprKind::LocalRef { ref ident, boxed } => {
1699 assert_eq!(ident.name, "x");
1700 assert_eq!(ident.span, Span::new(crate::FileId(0), 0, 1).unwrap());
1701 assert!(!boxed);
1702 }
1703 _ => panic!("expected LocalRef"),
1704 }
1705 }
1706
1707 #[test]
1708 fn global_ref_carries_name_and_mangled() {
1709 let mut ast = TypedAst::new();
1710 let mangled = crate::mangle::package_symbol("main", "y");
1711 let id = ast
1712 .try_push_expr(TypedExpr {
1713 kind: TypedExprKind::GlobalRef {
1714 mangled: mangled.clone(),
1715 name: Ident {
1716 name: "y".to_string(),
1717 span: Span::new(crate::FileId(0), 2, 3).unwrap(),
1718 },
1719 },
1720 span: Span::new(crate::FileId(0), 2, 3).unwrap(),
1721 ty: Type::Number,
1722 })
1723 .unwrap();
1724 match ast.try_expr(id).unwrap().kind {
1725 TypedExprKind::GlobalRef {
1726 ref name,
1727 ref mangled,
1728 } => {
1729 assert_eq!(name.name, "y");
1730 assert_eq!(name.span, Span::new(crate::FileId(0), 2, 3).unwrap());
1731 assert_eq!(mangled.as_str(), "main#y");
1732 }
1733 _ => panic!("expected GlobalRef"),
1734 }
1735 }
1736}