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 span: Span,
325 },
326 MethodCall {
327 iface: MangledName,
328 name: Ident,
329 args: Vec<ExprId>,
330 optional: bool,
331 result_ty: Type,
332 span: Span,
333 },
334 NonNull { result_ty: Type, span: Span },
338}
339
340impl TypedChainPart {
341 pub fn result_ty(&self) -> &Type {
343 match self {
344 TypedChainPart::Field { result_ty, .. }
345 | TypedChainPart::InterfaceProperty { result_ty, .. }
346 | TypedChainPart::Index { result_ty, .. }
347 | TypedChainPart::Call { result_ty, .. }
348 | TypedChainPart::MethodCall { result_ty, .. }
349 | TypedChainPart::NonNull { result_ty, .. } => result_ty,
350 }
351 }
352
353 pub fn set_result_ty(&mut self, ty: Type) {
357 match self {
358 TypedChainPart::Field { result_ty, .. }
359 | TypedChainPart::InterfaceProperty { result_ty, .. }
360 | TypedChainPart::Index { result_ty, .. }
361 | TypedChainPart::Call { result_ty, .. }
362 | TypedChainPart::MethodCall { result_ty, .. }
363 | TypedChainPart::NonNull { result_ty, .. } => *result_ty = ty,
364 }
365 }
366
367 pub fn is_optional(&self) -> bool {
369 match self {
370 TypedChainPart::Field { optional, .. }
371 | TypedChainPart::InterfaceProperty { optional, .. }
372 | TypedChainPart::Index { optional, .. }
373 | TypedChainPart::Call { optional, .. }
374 | TypedChainPart::MethodCall { optional, .. } => *optional,
375 TypedChainPart::NonNull { .. } => false,
376 }
377 }
378}
379
380#[derive(Clone, Debug, PartialEq)]
382pub struct GenericArgument {
383 pub expr: ExprId,
384 pub is_generic: bool,
385}
386
387#[derive(Clone, Debug, PartialEq)]
390pub struct CapturedVar {
391 pub name: Ident,
392 pub ty: Type,
393 pub boxed: bool,
394}
395
396#[derive(Clone, Debug, PartialEq)]
397pub enum ClosureBody {
398 Expr(ExprId),
399 Block(StmtId),
400}
401
402#[derive(Clone, Debug, PartialEq)]
403pub struct TypedObjectLiteralField {
404 pub name: Ident,
405 pub value: ExprId,
406}
407
408#[derive(Clone, Debug, PartialEq)]
409pub struct TypedObjectFieldOrigin {
410 pub name: Ident,
411 pub source: TypedObjectFieldSource,
412 pub optional: bool,
416 pub ty: Type,
422}
423
424pub fn object_literal_layout(ty: &Type, fields: &[TypedObjectFieldOrigin]) -> Type {
429 if let Type::Object { index: Some(_), .. } = ty {
430 return ty.clone();
431 }
432 let fields = fields
433 .iter()
434 .map(|field| {
435 (
436 field.name.name.clone(),
437 crate::ObjectField {
438 ty: field.ty.clone(),
439 optional: field.optional,
440 readonly: false,
441 method: false,
442 },
443 )
444 })
445 .collect();
446 Type::Object {
447 index: None,
448 fields,
449 }
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub enum TypedObjectFieldSource {
454 Literal(ExprId),
455 Absent(ExprId),
456 Spread {
459 source_index: usize,
460 field_name: String,
461 source_ty: Type,
462 fallback: Option<Box<TypedObjectFieldSource>>,
466 },
467}
468
469impl TypedObjectFieldSource {
470 pub fn literal_expr_id(&self) -> Option<ExprId> {
471 match self {
472 TypedObjectFieldSource::Literal(id) | TypedObjectFieldSource::Absent(id) => Some(*id),
473 TypedObjectFieldSource::Spread { .. } => None,
474 }
475 }
476}
477
478#[derive(Clone, Copy, Debug, PartialEq)]
480pub enum TypedObjectMember {
481 Computed {
482 key: ExprId,
483 value: ExprId,
484 },
485 Value(ExprId),
487 Spread {
492 source: ExprId,
493 by_name: bool,
494 },
495}
496
497impl TypedObjectMember {
498 pub fn expressions(self) -> impl Iterator<Item = ExprId> {
499 let key = match self {
500 Self::Computed { key, .. } => Some(key),
501 _ => None,
502 };
503 key.into_iter().chain(std::iter::once(self.expr_id()))
504 }
505
506 pub fn expr_id(self) -> ExprId {
507 match self {
508 TypedObjectMember::Value(id)
509 | TypedObjectMember::Spread { source: id, .. }
510 | TypedObjectMember::Computed { value: id, .. } => id,
511 }
512 }
513}
514
515#[derive(Clone, Debug, PartialEq)]
516pub enum TypedArrayElement {
517 Value(ExprId),
518 Spread(ExprId),
519}
520
521impl TypedArrayElement {
522 pub fn expr_id(&self) -> ExprId {
523 match self {
524 TypedArrayElement::Value(id) | TypedArrayElement::Spread(id) => *id,
525 }
526 }
527}
528
529#[derive(Copy, Clone, Debug, PartialEq, Eq)]
530pub enum TypeofTagKind {
531 Undefined,
532 BigInt,
533 Number,
534 String,
535 Boolean,
536 Object,
538 Function,
540}
541
542#[derive(Copy, Clone, Debug, PartialEq, Eq)]
543pub enum Intrinsic {
544 Assert,
546 JsonStringify,
548 JsonParse,
550 BigIntFromString,
552 ToString,
555}
556
557impl Intrinsic {
558 pub fn from_name(name: &str) -> Option<Self> {
559 match name {
560 "assert" => Some(Intrinsic::Assert),
561 _ => None,
562 }
563 }
564
565 pub fn params(self) -> Vec<crate::Param> {
568 use crate::{Param, Type};
569 match self {
570 Intrinsic::Assert => vec![
571 Param::new("condition", Type::Boolean),
572 Param {
573 name: "message".to_string(),
574 optional: true,
575 ty: Type::String,
576 default: Some(crate::DefaultValue::String("assertion failed".to_string())),
577 rest: false,
578 },
579 ],
580 Intrinsic::BigIntFromString => vec![Param::new("value", Type::String)],
581 Intrinsic::JsonStringify => vec![Param::new("value", Type::Error)],
582 Intrinsic::JsonParse => vec![Param::new("text", Type::String)],
583 Intrinsic::ToString => vec![Param::new("value", Type::Unknown)],
584 }
585 }
586
587 pub fn ret(self) -> crate::Type {
589 match self {
590 Intrinsic::Assert => crate::Type::Void,
591 Intrinsic::BigIntFromString => crate::Type::BigInt,
592 Intrinsic::JsonStringify => crate::Type::String,
593 Intrinsic::JsonParse => crate::Type::Unknown,
594 Intrinsic::ToString => crate::Type::String,
595 }
596 }
597
598 pub fn name(self) -> &'static str {
599 match self {
600 Intrinsic::Assert => "assert",
601 Intrinsic::BigIntFromString => "BigInt.fromString",
602 Intrinsic::JsonStringify => "JSON.stringify",
603 Intrinsic::JsonParse => "JSON.parse",
604 Intrinsic::ToString => "String",
605 }
606 }
607}
608
609#[derive(Clone, Debug, PartialEq)]
610pub struct TypedStmt {
611 pub kind: TypedStmtKind,
612 pub span: Span,
613}
614
615#[derive(Clone, Copy, Debug, PartialEq, Eq)]
618pub enum ForOfKind {
619 Array,
620 Iterator,
621 Iterable,
622}
623
624#[derive(Clone, Debug, PartialEq)]
625pub enum TypedStmtKind {
626 Let {
629 name: Ident,
630 ty: Type,
631 value: ExprId,
632 boxed: bool,
633 doc: Option<crate::DocComment>,
634 },
635 ReboxLocal {
645 ident: Ident,
646 ty: Type,
647 },
648 Const {
651 name: Ident,
652 ty: Type,
653 value: ExprId,
654 doc: Option<crate::DocComment>,
655 },
656 If {
657 condition: ExprId,
658 then_block: StmtId,
659 else_block: Option<StmtId>,
660 },
661 While {
662 condition: ExprId,
663 body: StmtId,
664 },
665 For {
667 init: Option<StmtId>,
668 condition: Option<ExprId>,
669 update: Option<StmtId>,
670 body: StmtId,
671 },
672 ForOf {
675 binding_kind: BindingKind,
676 name: Ident,
677 element_ty: Type,
678 iter: ExprId,
679 body: StmtId,
680 kind: ForOfKind,
681 },
682 DoWhile {
685 body: StmtId,
686 condition: ExprId,
687 },
688 Switch {
692 discriminant: ExprId,
693 discriminant_ty: Type,
694 cases: Vec<TypedSwitchCase>,
695 default: Option<StmtId>,
696 },
697 Break,
698 Continue,
702 Return(Option<ExprId>),
703 Throw {
705 value: ExprId,
706 },
707 Try {
711 body: StmtId,
712 catches: Vec<TypedCatchClause>,
713 finally: Option<StmtId>,
714 },
715 Expr(ExprId),
716 Block(Vec<StmtId>),
717 AssignLocal {
720 ident: Ident,
721 target_ty: Type,
722 value: ExprId,
723 boxed: bool,
724 narrowed_shadow_ty: Option<Type>,
728 },
729 AssignGlobal {
731 ident: Ident,
732 mangled: MangledName,
733 target_ty: Type,
734 value: ExprId,
735 },
736 AssignField {
737 receiver: ExprId,
738 name: Ident,
739 value: ExprId,
740 },
741 AssignIndex {
744 receiver: ExprId,
745 index: ExprId,
746 value: ExprId,
747 elem_ty: Type,
748 },
749 NarrowRegion {
752 path: ReferencePath,
753 source: ExprId,
754 binding: Ident,
755 cast_info: CastInfo,
756 body: StmtId,
757 },
758}
759
760#[derive(Clone, Debug, PartialEq)]
764pub struct TypedCatchClause {
765 pub binding: Ident,
766 pub ty: Type,
767 pub body: StmtId,
768 pub boxed: bool,
769 pub span: Span,
770}
771
772#[derive(Clone, Debug, PartialEq)]
775pub struct TypedSwitchCase {
776 pub values: Vec<TypedSwitchValue>,
777 pub body: StmtId,
778 pub span: Span,
779}
780
781impl TypedSwitchCase {
782 pub fn label_comparisons(&self) -> impl Iterator<Item = ExprId> + '_ {
785 self.values.iter().filter_map(|value| match value {
786 TypedSwitchValue::Expr { comparison, .. } => Some(*comparison),
787 _ => None,
788 })
789 }
790}
791
792#[derive(Clone, Debug, PartialEq)]
794pub enum TypedSwitchValue {
795 Expr {
798 label: ExprId,
799 comparison: ExprId,
802 literal: Option<crate::typechecker::infer::narrowing::LiteralValue>,
805 span: Span,
806 },
807 String {
808 value: String,
809 span: Span,
810 },
811 Number {
812 value: f64,
813 span: Span,
814 },
815 Boolean {
816 value: bool,
817 span: Span,
818 },
819 Null {
820 span: Span,
821 },
822 Undefined {
823 span: Span,
824 },
825 Enum {
827 enum_name: MangledName,
828 member: Ident,
829 value: EnumVariantPayload,
830 span: Span,
831 },
832}
833
834#[derive(Clone, Debug, PartialEq)]
836pub enum EnumVariantPayload {
837 Number(f64),
838 String(String),
839}
840
841#[derive(Clone, Debug, PartialEq)]
842pub struct TypedParam {
843 pub name: Ident,
844 pub optional: bool,
846 pub ty: Type,
847 pub boxed: bool,
851 pub rest: bool,
853 pub default: Option<crate::DefaultValue>,
856}
857
858#[derive(Default, Clone, Debug)]
859pub struct TypedAst {
860 pub parameter_default_prologues: std::collections::BTreeMap<StmtId, usize>,
862 pub closure_this: std::collections::BTreeMap<ExprId, Type>,
864 pub closure_names: std::collections::BTreeMap<ExprId, Ident>,
866 pub nested_function_names: std::collections::BTreeMap<ExprId, Ident>,
869 pub placeholder_closures: std::collections::BTreeSet<ExprId>,
873 pub authored_arguments: std::collections::BTreeMap<(u32, u32, u32), Vec<ExprId>>,
875 pub authored_parameter_types: std::collections::BTreeMap<(u32, u32, u32), Type>,
877 pub parameter_inputs: std::collections::BTreeSet<ExprId>,
879 pub parameter_initializations: std::collections::BTreeSet<StmtId>,
881 pub runtime_source_types: std::collections::BTreeMap<ExprId, Type>,
884 pub runtime_chain_types: std::collections::BTreeMap<ExprId, Vec<Type>>,
885 pub spread_mask_fields:
889 std::collections::BTreeMap<ExprId, std::collections::BTreeMap<String, Type>>,
890 pub spread_omitted_fields:
893 std::collections::BTreeMap<ExprId, std::collections::BTreeSet<String>>,
894 pub exhaustive_switches: std::collections::BTreeSet<ExprId>,
897 pub package_name: String,
899 exprs: Vec<TypedExpr>,
900 stmts: Vec<TypedStmt>,
901 pub globals: Vec<TypedGlobal>,
903 pub rebindable_globals: std::collections::BTreeMap<MangledName, String>,
909 pub functions: Vec<TypedFunction>,
911 pub top_level_statements: Vec<StmtId>,
913 pub class_declaration_points: std::collections::BTreeMap<MangledName, usize>,
916 pub types: Vec<TypedTypeDecl>,
920 pub shapes: Vec<crate::Shape>,
923 pub exports: Vec<ExportEntry>,
927 pub imported_packages: std::collections::BTreeSet<String>,
931 pub runtime_type_tests: std::collections::BTreeMap<Type, FieldNarrowingTest>,
935 pub runtime_class_fields: std::collections::BTreeMap<Type, Type>,
937 pub runtime_class_parameters: std::collections::BTreeMap<MangledName, Vec<String>>,
938 pub runtime_class_contexts: std::collections::BTreeMap<Type, Vec<InstanceTypeContext>>,
939 pub runtime_field_guards: std::collections::BTreeMap<Type, Vec<InstantiatedFieldGuard>>,
940}
941
942#[derive(Clone, Debug)]
943pub struct InstanceTypeContext {
944 pub declaration: MangledName,
945 pub args: Vec<Type>,
946}
947
948#[derive(Clone, Debug, PartialEq)]
951pub struct ExportEntry {
952 pub public_name: MangledName,
953 pub target: MangledName,
954 pub kind: ExportKind,
955 pub span: Span,
957}
958
959#[derive(Clone, Copy, Debug, PartialEq, Eq)]
960pub enum ExportKind {
961 Function,
962 Global,
963 Type,
964}
965
966#[derive(Clone, Debug, PartialEq)]
967pub struct TypedGlobal {
968 pub name: Ident,
969 pub mangled_name: MangledName,
970 pub ty: Type,
971 pub kind: GlobalKind,
972 pub doc: Option<crate::DocComment>,
973 pub span: Span,
974}
975
976#[derive(Copy, Clone, Debug, PartialEq, Eq)]
978pub enum GlobalKind {
979 Let,
980 Const,
981}
982
983#[derive(Clone, Debug, PartialEq)]
984pub struct TypedFunction {
985 pub name: Ident,
986 pub mangled_name: MangledName,
987 pub generics: Vec<String>,
990 pub params: Vec<TypedParam>,
991 pub return_type: Type,
992 pub type_predicate: Option<crate::TypePredicate>,
994 pub body: StmtId,
995 pub doc: Option<crate::DocComment>,
996 pub span: Span,
997}
998
999#[derive(Clone, Debug, PartialEq)]
1001pub enum TypedTypeDecl {
1002 Interface(TypedInterfaceDecl),
1003 Class(TypedClassDecl),
1004 NumberEnum(TypedNumberEnumDecl),
1005 StringEnum(TypedStringEnumDecl),
1006 Alias(TypedTypeAliasDecl),
1007}
1008
1009#[derive(Clone, Debug, PartialEq)]
1012pub struct TypedClassDecl {
1013 pub name: Ident,
1014 pub fields: Vec<TypedClassField>,
1015 pub static_methods: BTreeMap<String, crate::Visibility>,
1018 pub static_fields: BTreeMap<String, crate::FieldSig>,
1021 pub constructor: Option<TypedClassConstructor>,
1022 pub inherited_ctor_params: Vec<TypedParam>,
1031 pub methods: Vec<TypedClassMethod>,
1032 pub accessors: Vec<TypedClassAccessor>,
1037 pub extends: Option<crate::MangledName>,
1038 pub implements: Vec<crate::MangledName>,
1039 pub mangled_name: crate::MangledName,
1040 pub doc: Option<crate::DocComment>,
1041}
1042
1043#[derive(Clone, Debug, PartialEq)]
1046pub enum TypedClassAccessor {
1047 Getter {
1048 name: Ident,
1049 ret_ty: Type,
1050 visibility: crate::Visibility,
1051 body: StmtId,
1052 },
1053 Setter {
1054 name: Ident,
1055 param: TypedParam,
1057 visibility: crate::Visibility,
1058 body: StmtId,
1059 },
1060}
1061
1062impl TypedClassAccessor {
1063 pub fn name(&self) -> &Ident {
1064 match self {
1065 TypedClassAccessor::Getter { name, .. } | TypedClassAccessor::Setter { name, .. } => {
1066 name
1067 }
1068 }
1069 }
1070
1071 pub fn body(&self) -> StmtId {
1072 match self {
1073 TypedClassAccessor::Getter { body, .. } | TypedClassAccessor::Setter { body, .. } => {
1074 *body
1075 }
1076 }
1077 }
1078
1079 pub fn visibility(&self) -> crate::Visibility {
1080 match self {
1081 TypedClassAccessor::Getter { visibility, .. }
1082 | TypedClassAccessor::Setter { visibility, .. } => *visibility,
1083 }
1084 }
1085}
1086
1087#[derive(Clone, Debug, PartialEq)]
1088pub struct TypedClassField {
1089 pub name: Ident,
1090 pub ty: Type,
1091 pub visibility: crate::Visibility,
1092 pub readonly: bool,
1093 pub optional: bool,
1094 pub initializer: Option<ExprId>,
1095 pub auto_assigned: bool,
1099 pub narrowing_check: Option<Box<FieldNarrowingCheck>>,
1103 pub doc: Option<crate::DocComment>,
1104}
1105
1106#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1118pub struct FieldNarrowingCheck {
1119 #[serde(default)]
1120 pub declaration: Option<MangledName>,
1121 pub test: FieldNarrowingTest,
1122 #[serde(default)]
1125 pub minimal_test_target: Option<Type>,
1126 pub message: String,
1127}
1128
1129#[derive(Clone, Debug, PartialEq)]
1130pub struct InstantiatedFieldGuard {
1131 pub field: String,
1132 pub target: Type,
1133 pub check: FieldNarrowingCheck,
1134}
1135
1136#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1140pub enum FieldNarrowingTest {
1141 NonNull,
1153 Shape(Type),
1156 Interface(InterfaceNarrowingTest),
1161 Substituted,
1165 Representation,
1168}
1169
1170#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
1171pub struct InterfaceNarrowingTest {
1172 pub index: Option<crate::IndexSignature>,
1173 pub members: std::collections::BTreeMap<String, crate::ObjectField>,
1174 pub methods: std::collections::BTreeSet<String>,
1175 pub non_shape_carriers: std::collections::BTreeSet<InterfaceCarrier>,
1176 #[serde(default = "interface_shape_allowed_default")]
1180 pub shape_allowed: bool,
1181 pub nullable: bool,
1182 #[serde(default)]
1183 pub undefined: bool,
1184}
1185
1186fn interface_shape_allowed_default() -> bool {
1187 true
1188}
1189
1190#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
1191pub enum InterfaceCarrier {
1192 Number,
1193 Boolean,
1194 String,
1195 BigInt,
1196 Uint8Array,
1197 ArrayAny,
1198 Array(Type),
1199 MapAny,
1200 Map(Type, Type),
1201 SetAny,
1202 Set(Type),
1203 RegExp,
1204 RegExpMatch,
1205 TemporalInstant,
1206 TemporalDuration,
1207 TemporalZonedDateTime,
1208 TemporalPlainDate,
1209 TemporalPlainTime,
1210 TemporalPlainDateTime,
1211 TemporalPlainYearMonth,
1212 TemporalPlainMonthDay,
1213 ObjectShape,
1214 Url,
1215 FsStat,
1216 FsPeek,
1217 FsDirEntry,
1218 FsInfo,
1219 FsMountInfo,
1220 FsFileWriter,
1221 HttpResponse,
1222 HttpDownloadResult,
1223 SessionEntry,
1224 SessionPage,
1225}
1226
1227pub(crate) fn runtime_type_is_testable(ty: &Type) -> bool {
1231 runtime_type_is_testable_inner(ty, false)
1232}
1233
1234pub(crate) fn field_runtime_type_is_testable(ty: &Type) -> bool {
1239 runtime_type_is_testable_inner(ty, true)
1240}
1241
1242fn runtime_type_is_testable_inner(ty: &Type, allow_recursive_ref: bool) -> bool {
1243 match ty.peel() {
1244 Type::Null
1245 | Type::Undefined
1246 | Type::Void
1247 | Type::Number
1248 | Type::NumberLiteral(_)
1249 | Type::Boolean
1250 | Type::BooleanLiteral(_)
1251 | Type::String
1252 | Type::StringLiteral(_)
1253 | Type::BigInt
1254 | Type::Uint8Array
1255 | Type::Unknown
1256 | Type::Function { .. }
1257 | Type::NumberEnum { .. }
1258 | Type::StringEnum { .. }
1259 | Type::ClassRef { .. } => true,
1260 Type::AliasRef { .. } | Type::InterfaceRef { .. } => allow_recursive_ref,
1261 Type::Array(elem) => runtime_type_is_testable_inner(elem, allow_recursive_ref),
1262 Type::Tuple(elems) => elems
1263 .iter()
1264 .all(|elem| runtime_type_is_testable_inner(elem, allow_recursive_ref)),
1265 Type::Union(elems) => elems
1266 .iter()
1267 .all(|elem| runtime_type_is_testable_inner(elem, allow_recursive_ref)),
1268 Type::Object { fields, index } => {
1269 index
1270 .as_ref()
1271 .is_none_or(|i| runtime_type_is_testable_inner(&i.value, allow_recursive_ref))
1272 && fields
1273 .values()
1274 .all(|field| runtime_type_is_testable_inner(&field.ty, allow_recursive_ref))
1275 }
1276 _ => false,
1277 }
1278}
1279
1280impl TypedClassDecl {
1281 pub fn effective_ctor_params(&self) -> &[TypedParam] {
1286 match &self.constructor {
1287 Some(c) => &c.params,
1288 None => &self.inherited_ctor_params,
1289 }
1290 }
1291}
1292
1293#[derive(Clone, Debug, PartialEq)]
1294pub struct TypedClassConstructor {
1295 pub doc: Option<Box<crate::DocComment>>,
1296 pub span: Span,
1297 pub params: Vec<TypedParam>,
1298 pub body: StmtId,
1299}
1300
1301#[derive(Clone, Debug, PartialEq)]
1302pub struct TypedClassMethod {
1303 pub name: Ident,
1304 pub generics: Vec<String>,
1305 pub params: Vec<TypedParam>,
1306 pub return_type: Type,
1307 pub body: StmtId,
1308 pub visibility: crate::Visibility,
1309 pub doc: Option<crate::DocComment>,
1310}
1311
1312#[derive(Clone, Debug, PartialEq)]
1313pub struct TypedInterfaceDecl {
1314 pub property_names: std::collections::BTreeSet<String>,
1316 pub index: Option<crate::IndexSignature>,
1317 pub name: Ident,
1318 pub generics: Vec<String>,
1319 pub members: Vec<TypedInterfaceMember>,
1320 pub doc: Option<crate::DocComment>,
1321}
1322
1323#[derive(Clone, Debug, PartialEq)]
1324pub enum TypedInterfaceMember {
1325 Method {
1326 name: Ident,
1327 generics: Vec<String>,
1328 params: Vec<TypedParam>,
1329 return_type: Type,
1330 doc: Option<crate::DocComment>,
1331 },
1332 Property {
1333 name: Ident,
1334 ty: Type,
1335 readonly: bool,
1336 optional: bool,
1338 doc: Option<crate::DocComment>,
1339 },
1340}
1341
1342#[derive(Clone, Debug, PartialEq)]
1343pub struct TypedNumberEnumDecl {
1344 pub name: Ident,
1345 pub members: Vec<TypedNumberEnumMember>,
1346 pub doc: Option<crate::DocComment>,
1347}
1348
1349#[derive(Clone, Debug, PartialEq)]
1350pub struct TypedNumberEnumMember {
1351 pub name: Ident,
1352 pub value: f64,
1353 pub doc: Option<crate::DocComment>,
1354}
1355
1356#[derive(Clone, Debug, PartialEq)]
1357pub struct TypedStringEnumDecl {
1358 pub name: Ident,
1359 pub members: Vec<TypedStringEnumMember>,
1360 pub doc: Option<crate::DocComment>,
1361}
1362
1363#[derive(Clone, Debug, PartialEq)]
1364pub struct TypedStringEnumMember {
1365 pub name: Ident,
1366 pub value: String,
1367 pub doc: Option<crate::DocComment>,
1368}
1369
1370#[derive(Clone, Debug, PartialEq)]
1372pub struct TypedTypeAliasDecl {
1373 pub name: Ident,
1374 pub generics: Vec<String>,
1375 pub ty: Type,
1376 pub doc: Option<crate::DocComment>,
1377}
1378
1379impl TypedAst {
1380 pub(crate) fn has_string_index(&self, ty: &Type) -> bool {
1381 match ty.peel() {
1382 Type::Object { index, .. } => index.is_some(),
1383 Type::Union(members) => members.iter().any(|member| self.has_string_index(member)),
1384 Type::InterfaceRef { .. } => matches!(
1385 self.runtime_type_tests.get(ty.peel()),
1386 Some(FieldNarrowingTest::Interface(interface)) if interface.index.is_some()
1387 ),
1388 _ => false,
1389 }
1390 }
1391
1392 pub fn new() -> Self {
1393 Self {
1394 package_name: crate::mangle::USER_PACKAGE.to_string(),
1395 ..Self::default()
1396 }
1397 }
1398
1399 pub fn with_package(package_name: impl Into<String>) -> Self {
1400 Self {
1401 package_name: package_name.into(),
1402 ..Self::default()
1403 }
1404 }
1405
1406 pub fn record_authored_arguments(&mut self, span: Span, args: Vec<ExprId>) {
1407 self.authored_arguments
1408 .insert((span.file.0, span.start, span.end), args);
1409 }
1410
1411 pub fn authored_call_arguments(&self, span: Span) -> Option<&Vec<ExprId>> {
1412 self.authored_arguments
1413 .get(&(span.file.0, span.start, span.end))
1414 }
1415
1416 pub fn try_push_expr(&mut self, expr: TypedExpr) -> Result<ExprId, ArenaError> {
1418 arena::push(&mut self.exprs, expr, ArenaKind::TypedExpressions).map(ExprId)
1419 }
1420
1421 pub fn try_push_stmt(&mut self, stmt: TypedStmt) -> Result<StmtId, ArenaError> {
1423 arena::push(&mut self.stmts, stmt, ArenaKind::TypedStatements).map(StmtId)
1424 }
1425
1426 pub fn try_expr(&self, id: ExprId) -> Result<&TypedExpr, ArenaError> {
1427 arena::get(&self.exprs, id.0, ArenaKind::TypedExpressions)
1428 }
1429
1430 pub fn try_stmt(&self, id: StmtId) -> Result<&TypedStmt, ArenaError> {
1431 arena::get(&self.stmts, id.0, ArenaKind::TypedStatements)
1432 }
1433
1434 pub fn try_expr_mut(&mut self, id: ExprId) -> Result<&mut TypedExpr, ArenaError> {
1435 arena::get_mut(&mut self.exprs, id.0, ArenaKind::TypedExpressions)
1436 }
1437
1438 pub fn try_stmt_mut(&mut self, id: StmtId) -> Result<&mut TypedStmt, ArenaError> {
1439 arena::get_mut(&mut self.stmts, id.0, ArenaKind::TypedStatements)
1440 }
1441
1442 pub fn expr_ids(&self) -> Result<impl DoubleEndedIterator<Item = ExprId> + use<>, ArenaError> {
1444 Ok(arena::ids(self.exprs.len(), ArenaKind::TypedExpressions)?.map(ExprId))
1445 }
1446
1447 pub fn stmt_ids(&self) -> Result<impl DoubleEndedIterator<Item = StmtId> + use<>, ArenaError> {
1449 Ok(arena::ids(self.stmts.len(), ArenaKind::TypedStatements)?.map(StmtId))
1450 }
1451
1452 pub fn class_body_roots(&self) -> Vec<StmtId> {
1456 let mut roots = Vec::new();
1457 for decl in &self.types {
1458 if let TypedTypeDecl::Class(c) = decl {
1459 if let Some(ctor) = &c.constructor {
1460 roots.push(ctor.body);
1461 }
1462 roots.extend(c.methods.iter().map(|m| m.body));
1463 roots.extend(c.accessors.iter().map(TypedClassAccessor::body));
1464 }
1465 }
1466 roots
1467 }
1468
1469 pub fn class_field_initializers(&self) -> Vec<ExprId> {
1472 let mut inits = Vec::new();
1473 for decl in &self.types {
1474 if let TypedTypeDecl::Class(c) = decl {
1475 inits.extend(c.fields.iter().filter_map(|f| f.initializer));
1476 }
1477 }
1478 inits
1479 }
1480
1481 pub fn source_type(&self, id: ExprId) -> Result<&Type, ArenaError> {
1482 let expr = self.try_expr(id)?;
1483 Ok(self.runtime_source_types.get(&id).unwrap_or(&expr.ty))
1484 }
1485
1486 pub fn exprs_len(&self) -> usize {
1488 self.exprs.len()
1489 }
1490
1491 pub fn stmts_len(&self) -> usize {
1492 self.stmts.len()
1493 }
1494
1495 pub fn is_effect_free(&self, id: ExprId) -> Result<bool, ArenaError> {
1501 Ok(matches!(
1502 self.try_expr(id)?.kind,
1503 TypedExprKind::Number(_)
1504 | TypedExprKind::BigInt(_)
1505 | TypedExprKind::String(_)
1506 | TypedExprKind::Boolean(_)
1507 | TypedExprKind::Null
1508 | TypedExprKind::Undefined
1509 | TypedExprKind::This
1510 | TypedExprKind::Regex { .. }
1511 | TypedExprKind::LocalRef { .. }
1512 | TypedExprKind::LocalNarrowRef { .. }
1513 | TypedExprKind::GlobalRef { .. }
1514 | TypedExprKind::FunctionRef { .. }
1515 | TypedExprKind::NumberEnumMember { .. }
1516 | TypedExprKind::StringEnumMember { .. }
1517 ))
1518 }
1519}
1520
1521#[cfg(test)]
1522mod tests {
1523 use super::{TypedAst, TypedExpr, TypedExprKind, TypedParam, TypedStmt, TypedStmtKind};
1524 use crate::{BinOp, Ident, Span, Type, UnOp};
1525
1526 #[test]
1527 fn arena_round_trip_for_typed_exprs() {
1528 let mut ast = TypedAst::new();
1529 let id = ast
1530 .try_push_expr(TypedExpr {
1531 kind: TypedExprKind::Number(42.0),
1532 span: Span::new(crate::FileId(0), 0, 2).unwrap(),
1533 ty: Type::Number,
1534 })
1535 .unwrap();
1536 assert_eq!(id.0, 0);
1537 let e = ast.try_expr(id).unwrap();
1538 assert_eq!(e.kind, TypedExprKind::Number(42.0));
1539 assert_eq!(e.span, Span::new(crate::FileId(0), 0, 2).unwrap());
1540 assert_eq!(e.ty, Type::Number);
1541 }
1542
1543 #[test]
1544 fn arena_round_trip_for_typed_stmts() {
1545 let mut ast = TypedAst::new();
1546 let expr_id = ast
1547 .try_push_expr(TypedExpr {
1548 kind: TypedExprKind::Boolean(true),
1549 span: Span::new(crate::FileId(0), 0, 4).unwrap(),
1550 ty: Type::Boolean,
1551 })
1552 .unwrap();
1553 let stmt_id = ast
1554 .try_push_stmt(TypedStmt {
1555 kind: TypedStmtKind::Expr(expr_id),
1556 span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1557 })
1558 .unwrap();
1559 assert_eq!(stmt_id.0, 0);
1560 let s = ast.try_stmt(stmt_id).unwrap();
1561 assert_eq!(s.span, Span::new(crate::FileId(0), 0, 5).unwrap());
1562 assert!(matches!(s.kind, TypedStmtKind::Expr(_)));
1563 }
1564
1565 #[test]
1566 fn build_typed_binary_tree() {
1567 let mut ast = TypedAst::new();
1568 let lhs = ast
1569 .try_push_expr(TypedExpr {
1570 kind: TypedExprKind::Number(1.0),
1571 span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1572 ty: Type::Number,
1573 })
1574 .unwrap();
1575 let rhs = ast
1576 .try_push_expr(TypedExpr {
1577 kind: TypedExprKind::Number(2.0),
1578 span: Span::new(crate::FileId(0), 4, 5).unwrap(),
1579 ty: Type::Number,
1580 })
1581 .unwrap();
1582 let sum = ast
1583 .try_push_expr(TypedExpr {
1584 kind: TypedExprKind::Binary {
1585 op: BinOp::Add,
1586 lhs,
1587 rhs,
1588 },
1589 span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1590 ty: Type::Number,
1591 })
1592 .unwrap();
1593
1594 let outer = ast.try_expr(sum).unwrap();
1595 assert_eq!(outer.ty, Type::Number);
1596 let TypedExprKind::Binary { op, lhs, rhs } = outer.kind else {
1597 panic!("expected Binary");
1598 };
1599 assert_eq!(op, BinOp::Add);
1600 assert_eq!(ast.try_expr(lhs).unwrap().ty, Type::Number);
1601 assert_eq!(ast.try_expr(rhs).unwrap().ty, Type::Number);
1602 }
1603
1604 #[test]
1605 fn build_typed_function_with_return() {
1606 let mut ast = TypedAst::new();
1607 let one = ast
1608 .try_push_expr(TypedExpr {
1609 kind: TypedExprKind::Number(1.0),
1610 span: Span::new(crate::FileId(0), 31, 32).unwrap(),
1611 ty: Type::Number,
1612 })
1613 .unwrap();
1614 let ret = ast
1615 .try_push_stmt(TypedStmt {
1616 kind: TypedStmtKind::Return(Some(one)),
1617 span: Span::new(crate::FileId(0), 24, 33).unwrap(),
1618 })
1619 .unwrap();
1620 let body = ast
1621 .try_push_stmt(TypedStmt {
1622 kind: TypedStmtKind::Block(vec![ret]),
1623 span: Span::new(crate::FileId(0), 22, 35).unwrap(),
1624 })
1625 .unwrap();
1626 ast.functions.push(crate::TypedFunction {
1627 name: Ident {
1628 name: "f".to_string(),
1629 span: Span::new(crate::FileId(0), 9, 10).unwrap(),
1630 },
1631 mangled_name: crate::mangle::package_symbol("main", "f"),
1632 generics: vec![],
1633 params: vec![],
1634 return_type: Type::Number,
1635 type_predicate: None,
1636 body,
1637 doc: None,
1638 span: Span::new(crate::FileId(0), 0, 35).unwrap(),
1639 });
1640
1641 let f = &ast.functions[0];
1642 assert_eq!(f.return_type, Type::Number);
1643 assert!(matches!(
1644 ast.try_stmt(f.body).unwrap().kind,
1645 TypedStmtKind::Block(_)
1646 ));
1647 }
1648
1649 #[test]
1650 fn clone_and_equality() {
1651 let kind = TypedExprKind::Unary {
1652 op: UnOp::Neg,
1653 operand: crate::ExprId(0),
1654 };
1655 assert_eq!(kind.clone(), kind);
1656 }
1657
1658 #[test]
1659 fn typed_param_construction() {
1660 let p = TypedParam {
1661 optional: false,
1662 name: Ident {
1663 name: "param".to_string(),
1664 span: Span::new(crate::FileId(0), 10, 15).unwrap(),
1665 },
1666 ty: Type::String,
1667 boxed: false,
1668 rest: false,
1669 default: None,
1670 };
1671 assert_eq!(p.name.name, "param");
1672 assert_eq!(p.name.span, Span::new(crate::FileId(0), 10, 15).unwrap());
1673 assert_eq!(p.ty, Type::String);
1674 assert!(!p.boxed);
1675 }
1676
1677 #[test]
1678 fn local_ref_carries_name_and_boxed_flag() {
1679 let mut ast = TypedAst::new();
1680 let id = ast
1681 .try_push_expr(TypedExpr {
1682 kind: TypedExprKind::LocalRef {
1683 ident: Ident {
1684 name: "x".to_string(),
1685 span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1686 },
1687 boxed: false,
1688 },
1689 span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1690 ty: Type::Number,
1691 })
1692 .unwrap();
1693 match ast.try_expr(id).unwrap().kind {
1694 TypedExprKind::LocalRef { ref ident, boxed } => {
1695 assert_eq!(ident.name, "x");
1696 assert_eq!(ident.span, Span::new(crate::FileId(0), 0, 1).unwrap());
1697 assert!(!boxed);
1698 }
1699 _ => panic!("expected LocalRef"),
1700 }
1701 }
1702
1703 #[test]
1704 fn global_ref_carries_name_and_mangled() {
1705 let mut ast = TypedAst::new();
1706 let mangled = crate::mangle::package_symbol("main", "y");
1707 let id = ast
1708 .try_push_expr(TypedExpr {
1709 kind: TypedExprKind::GlobalRef {
1710 mangled: mangled.clone(),
1711 name: Ident {
1712 name: "y".to_string(),
1713 span: Span::new(crate::FileId(0), 2, 3).unwrap(),
1714 },
1715 },
1716 span: Span::new(crate::FileId(0), 2, 3).unwrap(),
1717 ty: Type::Number,
1718 })
1719 .unwrap();
1720 match ast.try_expr(id).unwrap().kind {
1721 TypedExprKind::GlobalRef {
1722 ref name,
1723 ref mangled,
1724 } => {
1725 assert_eq!(name.name, "y");
1726 assert_eq!(name.span, Span::new(crate::FileId(0), 2, 3).unwrap());
1727 assert_eq!(mangled.as_str(), "main#y");
1728 }
1729 _ => panic!("expected GlobalRef"),
1730 }
1731 }
1732}