Skip to main content

submilli_engine/
ast.rs

1use crate::arena::{self, ArenaError, ArenaKind};
2use crate::{DocComment, Span};
3
4#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
5pub struct ExprId(pub u32);
6
7#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
8pub struct StmtId(pub u32);
9
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct Ident {
12    pub name: String,
13    pub span: Span,
14}
15
16#[derive(Clone, Debug, PartialEq)]
17pub struct Expr {
18    pub kind: ExprKind,
19    pub span: Span,
20}
21
22#[derive(Clone, Debug, PartialEq)]
23pub enum ExprKind {
24    Number(f64),
25    /// Sign-free decimal digits; sign is the enclosing `Unary { op: Neg }`. Codegen packs into limbs.
26    BigInt(String),
27    String(String),
28    Boolean(bool),
29    Null,
30    Identifier(Ident),
31    Binary {
32        op: BinOp,
33        lhs: ExprId,
34        rhs: ExprId,
35    },
36    Unary {
37        op: UnOp,
38        operand: ExprId,
39    },
40    Call {
41        callee: ExprId,
42        type_args: Option<Vec<TypeAnnotation>>,
43        args: Vec<ExprId>,
44    },
45    Paren(ExprId),
46    ObjectLiteral {
47        members: Vec<ObjectLiteralMember>,
48    },
49    ArrayLiteral {
50        elements: Vec<ArrayLiteralElement>,
51    },
52    FieldAccess {
53        receiver: ExprId,
54        name: Ident,
55    },
56    IndexAccess {
57        receiver: ExprId,
58        index: ExprId,
59    },
60    FunctionExpression {
61        name: Option<Ident>,
62        this_type: Option<TypeAnnotation>,
63        function: ExprId,
64    },
65    Arrow {
66        params: Vec<ParamDecl>,
67        return_type: Option<TypeAnnotation>,
68        type_predicate: Option<TypePredicateAnnotation>,
69        body: ArrowBody,
70    },
71    /// Evaluate the operand for effects and produce `undefined`.
72    Void {
73        operand: ExprId,
74    },
75    /// `typeof x` — only valid against a string-literal tag. Folded into `TypedExprKind::TypeofTag`; never appears in the typed AST.
76    Typeof {
77        operand: ExprId,
78    },
79    /// `delete o.x` — parsed so the typechecker can reject it by name rather than
80    /// as a stray identifier; never appears in the typed AST.
81    Delete {
82        operand: ExprId,
83    },
84    /// `new Foo(args)`. Lowered to `TypedExprKind::MethodCall` by the typechecker; codegen never sees this variant.
85    New {
86        callee: ExprId,
87        type_args: Option<Vec<TypeAnnotation>>,
88        args: Vec<ExprId>,
89    },
90    /// `this` — a class receiver, a function receiver, or an arrow capture.
91    /// `Span` lives on the enclosing `Expr`.
92    This,
93    /// `this` parsed across a receiver boundary. Keep this distinction when
94    /// method shorthand is lowered to an arrow; inference owns the diagnostic.
95    ThisOutsideReceiver,
96    /// `super` — composes with `Call`/`FieldAccess` for `super(...)` / `super.method(...)`.
97    Super,
98    /// `parts.len() == exprs.len() + 1`. No-substitution `` `plain` `` is collapsed to `String` by the parser.
99    TemplateLiteral {
100        parts: Vec<String>,
101        exprs: Vec<ExprId>,
102        /// Each substitution's span, `${` through `}`, parallel to `exprs`.
103        substitution_spans: Vec<Span>,
104    },
105    Ternary {
106        cond: ExprId,
107        then_: ExprId,
108        else_: ExprId,
109    },
110    OptionalChain {
111        base: ExprId,
112        parts: Vec<ChainPart>,
113    },
114    PostfixUnary {
115        op: PostfixOp,
116        operand: ExprId,
117    },
118    /// An assignment used as a value: `b = (a = 3)`, `while ((m = next()) !== null)`.
119    /// It yields the assigned value. One in statement position is parsed as an
120    /// assignment statement instead. `target` is an identifier, field access, or
121    /// index access; `op` is set for a compound assignment (`+=`).
122    Assign {
123        target: ExprId,
124        op: Option<BinOp>,
125        op_span: Span,
126        value: ExprId,
127    },
128    /// `x as T` — runtime-checked, unlike TypeScript's unchecked `as`. Codegen emits `ref.test`; throws `Error` on mismatch.
129    As {
130        expr: ExprId,
131        ty: TypeAnnotation,
132    },
133    /// `x instanceof Foo` — runtime nominal class test: a ref.eq walk over the
134    /// vtable-singleton parent chain (docs/classes.md §9). The right-hand side
135    /// names a class type, not a value.
136    InstanceOf {
137        value: ExprId,
138        ty: TypeAnnotation,
139    },
140    /// Regex literal. Pattern and flags validated at compile time; `Diagnostic` on invalid pattern or flag.
141    Regex {
142        source: String,
143        flags: String,
144    },
145}
146
147/// Postfix only — prefix `++x`/`--x` is not supported.
148#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
149pub enum PostfixOp {
150    Inc,
151    Dec,
152    NonNullAssert,
153}
154
155#[derive(Clone, Debug, PartialEq)]
156pub enum ChainPart {
157    Field {
158        name: Ident,
159        optional: bool,
160        span: Span,
161    },
162    Index {
163        idx: ExprId,
164        optional: bool,
165        span: Span,
166    },
167    Call {
168        args: Vec<ExprId>,
169        type_args: Option<Vec<TypeAnnotation>>,
170        optional: bool,
171        span: Span,
172    },
173    /// A `!` continuation (`a?.b!.c`). Carries no `optional` flag: `!` asserts the
174    /// value it is applied to is non-null, so it never short-circuits.
175    NonNull { span: Span },
176}
177
178impl ChainPart {
179    pub fn is_optional(&self) -> bool {
180        match self {
181            ChainPart::Field { optional, .. }
182            | ChainPart::Index { optional, .. }
183            | ChainPart::Call { optional, .. } => *optional,
184            ChainPart::NonNull { .. } => false,
185        }
186    }
187
188    /// This step with its `?.` dropped, for a receiver that can't be `null`.
189    pub fn as_plain_step(mut self) -> Self {
190        match &mut self {
191            ChainPart::Field { optional, .. }
192            | ChainPart::Index { optional, .. }
193            | ChainPart::Call { optional, .. } => *optional = false,
194            ChainPart::NonNull { .. } => {}
195        }
196        self
197    }
198
199    pub fn span(&self) -> Span {
200        match self {
201            ChainPart::Field { span, .. }
202            | ChainPart::Index { span, .. }
203            | ChainPart::Call { span, .. }
204            | ChainPart::NonNull { span } => *span,
205        }
206    }
207}
208
209#[derive(Clone, Copy, Debug, PartialEq)]
210pub enum ArrowBody {
211    Expr(ExprId),
212    Block(StmtId),
213}
214
215#[derive(Clone, Debug, PartialEq)]
216pub struct ObjectLiteralField {
217    pub name: Ident,
218    pub value: ExprId,
219}
220
221#[derive(Clone, Debug, PartialEq)]
222pub enum ObjectLiteralMember {
223    Computed {
224        key: ExprId,
225        value: ExprId,
226    },
227    Field(ObjectLiteralField),
228    Spread {
229        value: ExprId,
230        /// Span of the `...` token plus operand.
231        span: Span,
232    },
233}
234
235impl ObjectLiteralMember {
236    pub fn expressions(&self) -> impl Iterator<Item = ExprId> {
237        let key = match self {
238            Self::Computed { key, .. } => Some(*key),
239            _ => None,
240        };
241        key.into_iter().chain(std::iter::once(self.value()))
242    }
243
244    pub fn value(&self) -> ExprId {
245        match self {
246            ObjectLiteralMember::Field(f) => f.value,
247            ObjectLiteralMember::Spread { value, .. }
248            | ObjectLiteralMember::Computed { value, .. } => *value,
249        }
250    }
251}
252
253#[derive(Clone, Debug, PartialEq)]
254pub enum ArrayLiteralElement {
255    Value(ExprId),
256    Spread {
257        value: ExprId,
258        /// Span of the `...` token plus operand.
259        span: Span,
260    },
261}
262
263impl ArrayLiteralElement {
264    pub fn value(&self) -> ExprId {
265        match self {
266            ArrayLiteralElement::Value(id) => *id,
267            ArrayLiteralElement::Spread { value, .. } => *value,
268        }
269    }
270}
271
272#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
273pub enum BinOp {
274    Add,
275    Sub,
276    Mul,
277    Div,
278    Rem,
279    /// `a ** b` — right-associative exponentiation.
280    Pow,
281    BitAnd,
282    BitOr,
283    BitXor,
284    Shl,
285    Shr,
286    UnsignedShr,
287    Eq,
288    NotEq,
289    Lt,
290    Gt,
291    Le,
292    Ge,
293    And,
294    Or,
295    In,
296    /// `a ?? b` — nullish coalescing. Parser rejects mixing with `||`/`&&` without parentheses.
297    NullishCoalesce,
298}
299
300#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
301pub enum UnOp {
302    Not,
303    BitNot,
304    Neg,
305    Pos,
306}
307
308impl BinOp {
309    pub(crate) fn bitwise_name(self) -> Option<&'static str> {
310        match self {
311            Self::BitAnd => Some("bitand"),
312            Self::BitOr => Some("bitor"),
313            Self::BitXor => Some("bitxor"),
314            Self::Shl => Some("shl"),
315            Self::Shr => Some("shr"),
316            Self::UnsignedShr => Some("ushr"),
317            _ => None,
318        }
319    }
320}
321
322#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
323pub enum BindingKind {
324    Let,
325    Const,
326}
327
328#[derive(Clone, Debug, PartialEq)]
329pub struct Stmt {
330    pub kind: StmtKind,
331    pub span: Span,
332}
333
334#[derive(Clone, Debug, PartialEq)]
335pub enum StmtKind {
336    Let {
337        name: Ident,
338        ty: Option<TypeAnnotation>,
339        value: ExprId,
340        doc: Option<DocComment>,
341    },
342    Const {
343        name: Ident,
344        ty: Option<TypeAnnotation>,
345        value: ExprId,
346        doc: Option<DocComment>,
347    },
348    /// Destructuring `let`. Eliminated by `lower_patterns` into plain `Let` stmts; never reaches the typechecker.
349    LetPattern {
350        binding: Binding,
351        ty: Option<TypeAnnotation>,
352        value: ExprId,
353        doc: Option<DocComment>,
354    },
355    ConstPattern {
356        binding: Binding,
357        ty: Option<TypeAnnotation>,
358        value: ExprId,
359        doc: Option<DocComment>,
360    },
361    /// Rest binding from `const { a, ...rest } = obj` or its `let` form. `name` gets a narrowed type with `exclude` fields removed; codegen treats it like `Const` or `Let`.
362    ObjectRest {
363        is_const: bool,
364        name: Ident,
365        source: ExprId,
366        exclude: Vec<Ident>,
367        ty: Option<TypeAnnotation>,
368        doc: Option<DocComment>,
369    },
370    Function {
371        name: Ident,
372        generics: Vec<TypeParameterAnnotation>,
373        params: Vec<ParamDecl>,
374        /// `None` when a type predicate is declared; exactly one of `return_type` / `type_predicate` is `Some` after parse.
375        return_type: Option<TypeAnnotation>,
376        type_predicate: Option<TypePredicateAnnotation>,
377        body: StmtId,
378        doc: Option<DocComment>,
379    },
380    If {
381        condition: ExprId,
382        then_block: StmtId,
383        else_block: Option<StmtId>,
384    },
385    While {
386        condition: ExprId,
387        body: StmtId,
388    },
389    /// C-style `for` loop. `update` is a `StmtId` so an assignment there is an assignment statement.
390    For {
391        /// One statement per declarator of a `let`/`const` head, or per
392        /// comma-separated expression; empty without an initializer.
393        init: Vec<StmtId>,
394        condition: Option<ExprId>,
395        update: Option<StmtId>,
396        /// Always a `Block` — parser requires `{ … }`.
397        body: StmtId,
398    },
399    ForOf {
400        binding_kind: BindingKind,
401        name: Ident,
402        ty: Option<TypeAnnotation>,
403        iter: ExprId,
404        /// Always a `Block` — parser requires `{ … }`.
405        body: StmtId,
406    },
407    /// Destructuring `for-of` head. Eliminated by `lower_patterns`; never reaches the typechecker.
408    ForOfPattern {
409        binding_kind: BindingKind,
410        binding: Binding,
411        ty: Option<TypeAnnotation>,
412        iter: ExprId,
413        /// Always a `Block` — parser requires `{ … }`.
414        body: StmtId,
415    },
416    DoWhile {
417        /// Always a `Block` — parser requires `{ … }`.
418        body: StmtId,
419        condition: ExprId,
420    },
421    /// No fallthrough — typechecker rejects a case body that doesn't end in `break` or `return`.
422    Switch {
423        discriminant: ExprId,
424        cases: Vec<SwitchCase>,
425        default: Option<SwitchDefault>,
426    },
427    Break,
428    Continue,
429    Return(Option<ExprId>),
430    Throw {
431        value: ExprId,
432    },
433    /// `catches` may be empty and `finally` optional, but at least one must appear.
434    /// Multiple `catch` clauses dispatch in declaration order on the thrown
435    /// value's nominal class; the typechecker rejects unreachable arms.
436    Try {
437        body: StmtId,
438        catches: Vec<CatchClause>,
439        finally: Option<StmtId>,
440    },
441    Expr(ExprId),
442    Block(Vec<StmtId>),
443    Assign {
444        target: Ident,
445        value: ExprId,
446    },
447    AssignField {
448        receiver: ExprId,
449        field_name: Ident,
450        value: ExprId,
451    },
452    AssignIndex {
453        receiver: ExprId,
454        index: ExprId,
455        value: ExprId,
456    },
457    /// `target += value;` etc. Lowered to `Assign` with a synthesized `Binary`; `op_span` anchors infer diagnostics.
458    CompoundAssign {
459        target: Ident,
460        op: BinOp,
461        op_span: Span,
462        value: ExprId,
463    },
464    CompoundAssignField {
465        receiver: ExprId,
466        field_name: Ident,
467        op: BinOp,
468        op_span: Span,
469        value: ExprId,
470    },
471    CompoundAssignIndex {
472        receiver: ExprId,
473        index: ExprId,
474        op: BinOp,
475        op_span: Span,
476        value: ExprId,
477    },
478    /// Interface declaration. Single declaration site per name; no reopening.
479    InterfaceDecl {
480        name: Ident,
481        generics: Vec<TypeParameterAnnotation>,
482        extends: Vec<TypeAnnotation>,
483        members: Vec<InterfaceMember>,
484        doc: Option<DocComment>,
485    },
486    /// Class declaration. Single inheritance via `extends`, structural `implements`.
487    /// `generics` is parsed and stored for forward-compat; generic classes are not yet
488    /// consumed downstream (classes.md non-goals).
489    ClassDecl {
490        name: Ident,
491        generics: Vec<TypeParameterAnnotation>,
492        extends: Option<TypeAnnotation>,
493        implements: Vec<TypeAnnotation>,
494        members: Vec<ClassMember>,
495        doc: Option<DocComment>,
496    },
497    /// Enum declaration. No const enums, no computed members. `value: None` means auto-numbered at typecheck time.
498    EnumDecl {
499        name: Ident,
500        members: Vec<EnumMember>,
501        doc: Option<DocComment>,
502    },
503    TypeAliasDecl {
504        name: Ident,
505        generics: Vec<TypeParameterAnnotation>,
506        ty: TypeAnnotation,
507        doc: Option<DocComment>,
508    },
509    /// Top-level only; nested imports are a parse error.
510    Import {
511        module: String,
512        module_span: Span,
513        kind: ImportKind,
514        doc: Option<DocComment>,
515    },
516    /// Re-export (`export { a, b as c } from "./util";` or `export { x };`).
517    /// Top-level only. Parses in single-file but the typechecker gates it
518    /// until cross-module packages land. `source` is the `from "…"` module
519    /// path + span, or `None` for the bare `export { x };` form.
520    ExportFrom {
521        specs: Vec<ImportSpecifier>,
522        source: Option<(String, Span)>,
523        doc: Option<DocComment>,
524    },
525}
526
527#[derive(Clone, Debug, PartialEq)]
528pub enum ImportKind {
529    Named(Vec<ImportSpecifier>),
530    /// Compile-time namespace — namespace value is not first-class (`const x = uuid;` is rejected).
531    Namespace {
532        local_name: Ident,
533    },
534}
535
536#[derive(Clone, Debug, PartialEq)]
537pub struct TypeParameterAnnotation {
538    pub ident: Ident,
539    pub constraint: Option<TypeAnnotation>,
540}
541
542impl std::ops::Deref for TypeParameterAnnotation {
543    type Target = Ident;
544
545    fn deref(&self) -> &Ident {
546        &self.ident
547    }
548}
549
550impl From<Ident> for TypeParameterAnnotation {
551    fn from(ident: Ident) -> Self {
552        Self {
553            ident,
554            constraint: None,
555        }
556    }
557}
558
559#[derive(Clone, Debug, PartialEq)]
560pub struct ImportSpecifier {
561    pub imported_name: Ident,
562    pub local_name: Ident,
563}
564
565#[derive(Clone, Debug, PartialEq)]
566pub enum InterfaceMember {
567    IndexSignature(IndexSignatureAnnotation),
568    Method {
569        name: Ident,
570        optional: bool,
571        generics: Vec<TypeParameterAnnotation>,
572        params: Vec<ParamDecl>,
573        return_type: TypeAnnotation,
574        span: Span,
575        doc: Option<DocComment>,
576    },
577    /// Property. `readonly` forbids writes through the interface; `optional: true`
578    /// widens reads to `T | undefined`.
579    Property {
580        name: Ident,
581        ty: TypeAnnotation,
582        optional: bool,
583        readonly: bool,
584        span: Span,
585        doc: Option<DocComment>,
586    },
587}
588
589/// Member visibility. No `protected` — it is rejected at parse time.
590#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
591pub enum Visibility {
592    #[default]
593    Public,
594    Private,
595}
596
597impl Visibility {
598    pub fn is_public(&self) -> bool {
599        *self == Visibility::Public
600    }
601}
602
603/// Modifiers on a class field or method. `readonly`/`visibility_span` carry spans so
604/// later phases can anchor diagnostics (e.g. assignment to a `readonly` field).
605#[derive(Clone, Debug, PartialEq)]
606pub struct ClassModifiers {
607    pub visibility: Visibility,
608    pub visibility_span: Option<Span>,
609    pub readonly: Option<Span>,
610    pub static_span: Option<Span>,
611}
612
613#[derive(Clone, Debug, PartialEq)]
614pub enum ClassMember {
615    Field {
616        name: Ident,
617        modifiers: ClassModifiers,
618        /// `x?: T`.
619        optional: bool,
620        /// `f?(): T;`: an optional method declared without a body, held as a
621        /// field of its function type that a subclass method may fill.
622        method_signature: bool,
623        ty: TypeAnnotation,
624        /// `= expr`. Semantics owned by the typechecker.
625        initializer: Option<ExprId>,
626        span: Span,
627        doc: Option<DocComment>,
628    },
629    Method {
630        name: Ident,
631        optional: bool,
632        modifiers: ClassModifiers,
633        generics: Vec<TypeParameterAnnotation>,
634        params: Vec<ParamDecl>,
635        return_type: TypeAnnotation,
636        body: StmtId,
637        span: Span,
638        doc: Option<DocComment>,
639    },
640    Constructor {
641        /// `private constructor`: only the class's own module may call it or
642        /// extend the class.
643        visibility: Visibility,
644        params: Vec<ParamDecl>,
645        body: StmtId,
646        span: Span,
647        doc: Option<DocComment>,
648    },
649    /// `get x(): T { … }` or `set x(v: T) { … }`. Get/set of the same name pair
650    /// into one property; the typechecker validates and pairs them.
651    Accessor {
652        name: Ident,
653        modifiers: ClassModifiers,
654        kind: AccessorKind,
655        /// The setter's single parameter; `None` for a getter. Boxed to keep the
656        /// variant near its siblings' size (clippy `large_enum_variant`).
657        param: Option<Box<ParamDecl>>,
658        /// The getter's return type; `None` for a setter.
659        return_type: Option<TypeAnnotation>,
660        body: StmtId,
661        span: Span,
662        doc: Option<DocComment>,
663    },
664}
665
666#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
667pub enum AccessorKind {
668    Get,
669    Set,
670}
671
672#[derive(Clone, Debug, PartialEq)]
673pub struct EnumMember {
674    pub name: Ident,
675    pub value: Option<EnumInitializer>,
676    pub span: Span,
677    pub doc: Option<DocComment>,
678}
679
680/// Enum member initializer. Restricted to numeric and string literals (numeric may be negated).
681#[derive(Clone, Debug, PartialEq)]
682pub enum EnumInitializer {
683    /// `= 1` or `= -1` — `span` covers the leading minus when present.
684    Number {
685        value: f64,
686        span: Span,
687    },
688    String {
689        value: String,
690        span: Span,
691    },
692}
693
694impl EnumInitializer {
695    pub fn span(&self) -> Span {
696        match self {
697            EnumInitializer::Number { span, .. } => *span,
698            EnumInitializer::String { span, .. } => *span,
699        }
700    }
701}
702
703/// `values` has multiple entries when consecutive `case` labels share a body.
704#[derive(Clone, Debug, PartialEq)]
705pub struct SwitchCase {
706    pub values: Vec<ExprId>,
707    pub body: StmtId,
708    pub span: Span,
709}
710
711/// Separate from `SwitchCase` so duplicate-default detection has nowhere to slip.
712#[derive(Clone, Debug, PartialEq)]
713pub struct SwitchDefault {
714    pub body: StmtId,
715    pub span: Span,
716}
717
718/// `catch (e)` and bindingless `catch` are catch-all clauses.
719/// A bindingless clause uses an inaccessible compiler identifier.
720/// When present, the typechecker verifies `ty` is an Error class.
721#[derive(Clone, Debug, PartialEq)]
722pub struct CatchClause {
723    pub binding: Ident,
724    pub ty: Option<TypeAnnotation>,
725    pub body: StmtId,
726    pub span: Span,
727}
728
729/// Source-syntax parameter. Fully-resolved form is [`crate::Param`].
730#[derive(Clone, Debug, PartialEq)]
731pub struct ParamDecl {
732    pub name: Ident,
733    pub ty: Option<TypeAnnotation>,
734    pub default: Option<ExprId>,
735    /// Declared with `?`. A parameter with a default may be omitted too;
736    /// [`ParamDecl::is_omittable`] asks that.
737    pub optional: bool,
738    /// Cleared by `lower_patterns`; always `None` post-lowering.
739    pub pattern: Option<Binding>,
740    pub rest: bool,
741    /// `Some` for a constructor parameter property (`constructor(public x: T)`):
742    /// declares and auto-assigns a field. `None` for an ordinary parameter.
743    pub modifiers: Option<ClassModifiers>,
744}
745
746impl ParamDecl {
747    /// Whether a caller may leave the argument out: declared `?`, or defaulted.
748    pub fn is_omittable(&self) -> bool {
749        self.optional || self.default.is_some()
750    }
751}
752
753/// Destructuring binding pattern. Plain `Ident` bindings stay as `Let`/`Const` stmts; only `{…}` / `[…]` forms appear here.
754#[derive(Clone, Debug, PartialEq)]
755pub enum Binding {
756    Object {
757        fields: Vec<ObjectPatternField>,
758        rest: Option<Ident>,
759        span: Span,
760    },
761    Array {
762        /// `None` entries are holes (`[, x]`).
763        elems: Vec<Option<Ident>>,
764        /// Defaults at the corresponding element positions; holes have no default.
765        defaults: Vec<Option<ExprId>>,
766        rest: Option<Ident>,
767        span: Span,
768    },
769}
770
771impl Binding {
772    pub fn span(&self) -> Span {
773        match self {
774            Binding::Object { span, .. } | Binding::Array { span, .. } => *span,
775        }
776    }
777}
778
779/// `source` is the field name on the RHS; `local` is the binding name. Equal for shorthand `{ a }`, differ for `{ a: x }`.
780#[derive(Clone, Debug, PartialEq)]
781pub struct ObjectPatternField {
782    pub source: Ident,
783    pub local: Ident,
784    pub default: Option<ExprId>,
785    pub span: Span,
786}
787
788#[derive(Clone, Debug, PartialEq)]
789pub struct TypeAnnotation {
790    pub kind: TypeAnnotationKind,
791    pub span: Span,
792}
793
794/// Surface form of `<param> is <T>`. Only valid at a function return-type position.
795#[derive(Clone, Debug, PartialEq)]
796pub struct TypePredicateAnnotation {
797    pub param: Ident,
798    pub asserted: TypeAnnotation,
799    pub span: Span,
800}
801
802#[derive(Clone, Debug, PartialEq)]
803pub enum TypeAnnotationKind {
804    /// `name.span` covers the identifier only; outer span includes any `<…>` args.
805    Name {
806        name: Ident,
807        args: Vec<TypeAnnotation>,
808    },
809    /// Dotted type like `Temporal.Instant`. `path.len() >= 2`; last segment is the type name.
810    Qualified {
811        path: Vec<Ident>,
812        args: Vec<TypeAnnotation>,
813    },
814    StringLiteral(String),
815    TemplateLiteral {
816        parts: Vec<String>,
817        substitutions: Vec<TypeAnnotation>,
818    },
819    /// Parser canonicalizes `-0.0` to `0.0`.
820    NumberLiteral(crate::types::LiteralF64),
821    /// Decimal digits with a leading `-` when negative, as in [`crate::Type::BigIntLiteral`].
822    BigIntLiteral(String),
823    BooleanLiteral(bool),
824    Array(Box<TypeAnnotation>),
825    /// Element labels (`[x: number, y: number]`) are documentation only, so the
826    /// parser checks and drops them.
827    Tuple(Vec<TypeAnnotation>),
828    /// An omittable tuple element (`T?` or `name?: T`).
829    Optional(Box<TypeAnnotation>),
830    /// `readonly T[]` / `readonly [A, B]`. The parser only builds this around an
831    /// [`Array`](Self::Array) or [`Tuple`](Self::Tuple) operand.
832    Readonly(Box<TypeAnnotation>),
833    Object {
834        fields: Vec<TypeAnnotationField>,
835        index: Option<Box<IndexSignatureAnnotation>>,
836    },
837    /// Function type annotation. Only the named-parameter form is accepted.
838    Function {
839        params: Vec<TypeAnnotationField>,
840        return_type: Box<TypeAnnotation>,
841    },
842    /// Always ≥2 members; single-element unions are unwrapped by the parser.
843    Union(Vec<TypeAnnotation>),
844    /// `keyof T` — the union of `T`'s member names as string literal types.
845    KeyOf(Box<TypeAnnotation>),
846    /// `T[K]` — the type of `T`'s property `K`.
847    IndexedAccess {
848        object: Box<TypeAnnotation>,
849        index: Box<TypeAnnotation>,
850    },
851    /// `typeof x` — the type of the *value* `x`, looked up in the value namespace
852    /// rather than the type namespace. `path` is the dotted reference, one span per
853    /// segment (`typeof o.k` has two). Resolved eagerly.
854    TypeOf {
855        path: Vec<Ident>,
856    },
857}
858
859/// Reused for object-type fields and function-type parameters. `rest` is always
860/// `false` in object position; `readonly` is only meaningful for object-type fields
861/// (always `false` for function/tuple parameters).
862#[derive(Clone, Debug, PartialEq)]
863pub struct TypeAnnotationField {
864    pub name: Ident,
865    pub ty: TypeAnnotation,
866    pub optional: bool,
867    pub readonly: bool,
868    pub rest: bool,
869    /// Declared with method syntax, `m(): T`, rather than as a property `m: () => T`.
870    pub method: bool,
871}
872
873/// A destructuring default, lowered to `saved === undefined ? default : saved`.
874#[derive(Clone, Debug, PartialEq, Eq)]
875pub struct BindingDefault {
876    /// The component read, saved once before the default is chosen.
877    pub saved: Ident,
878    /// The default expression.
879    pub default: ExprId,
880    /// Whether the pattern's root has a type annotation, which the default
881    /// must then fit.
882    pub annotated: bool,
883}
884
885/// Metadata for a synthesized `IndexAccess` from pattern lowering — lets the typechecker emit destructure-specific errors.
886#[derive(Clone, Debug, PartialEq, Eq)]
887pub struct PatternOrigin {
888    pub pattern_span: Span,
889    pub slot_arity: usize,
890    /// The slot has a default, so a read past a tuple's end is `undefined`
891    /// rather than an error, as in tsc; lowering guards the read by length.
892    pub defaulted: bool,
893}
894
895/// A top-level declaration marked `export` (Form 1: `export function …`,
896/// `export const …`, etc.). The declaration itself stays a plain node in
897/// `top_level`; this side record only tracks which ones are export-marked, so
898/// the inference passes need no `export` awareness.
899#[derive(Clone, Debug)]
900pub struct ExportedDecl {
901    pub stmt: StmtId,
902    /// Span of the `export` keyword — used for diagnostics and `ExportEntry`.
903    pub export_span: Span,
904}
905
906#[derive(Default, Clone, Debug)]
907pub struct Ast {
908    exprs: Vec<Expr>,
909    stmts: Vec<Stmt>,
910    pub top_level: Vec<StmtId>,
911    /// Call argument operands written with a leading `...`.
912    pub call_spread_args: std::collections::BTreeSet<ExprId>,
913    /// Maps synthesized `IndexAccess` IDs → their source pattern (populated by `lower_patterns`).
914    /// `BTreeMap` is precautionary: the compiler only ever looks entries up by `ExprId`, and
915    /// the parser snapshots that render `Ast`'s `Debug` all run before `lower_patterns`
916    /// populates this, so order is unobserved today. An ordered map keeps any future walk —
917    /// or post-lowering snapshot — independent of the hash seed.
918    pub pattern_origins: std::collections::BTreeMap<ExprId, PatternOrigin>,
919    /// Array literals that an array pattern destructures directly, as in
920    /// `let [a, b] = [1, "s"]`. TypeScript types such a literal as a tuple from
921    /// the pattern, so each name gets its element's type and a pattern longer
922    /// than the literal is a compile error (populated by `lower_patterns`).
923    pub tuple_pattern_sources: std::collections::BTreeSet<ExprId>,
924    /// For a parameter's tuple literal default shorter than its pattern, the
925    /// defaults of the slots past its end (`None` for a hole). TypeScript pads
926    /// the default's type with an optional element for each, so a caller may
927    /// pass them.
928    pub tuple_pattern_pad_defaults: std::collections::BTreeMap<ExprId, Vec<Option<ExprId>>>,
929    /// For a parameter's object literal default, the properties its pattern
930    /// gives defaults that the literal lacks, with those defaults:
931    /// `{ a = 1 } = {}`. TypeScript adds each to the default's type as an
932    /// optional property, so a caller may pass it.
933    pub object_pattern_pad_defaults: std::collections::BTreeMap<ExprId, Vec<(String, ExprId)>>,
934    /// Unannotated object literals an object pattern takes apart, with the
935    /// properties the pattern gives defaults and their defaults:
936    /// `{ a, b = 0 } = { a: 1, b: 2 }`. TypeScript types such a literal from
937    /// the pattern, so those properties are optional in its type (populated by
938    /// `lower_patterns`). A default that may read a name the pattern binds is
939    /// `None`: it can't be typed ahead of the pattern as the property's context.
940    pub object_pattern_sources:
941        std::collections::BTreeMap<ExprId, std::collections::BTreeMap<String, Option<ExprId>>>,
942    /// For a tuple pattern source, the defaults of the pattern's slots the
943    /// literal fills, by index: `[a = 1] = [2]`.
944    pub tuple_pattern_defaults:
945        std::collections::BTreeMap<ExprId, std::collections::BTreeMap<usize, ExprId>>,
946    /// Pattern sources a `const` declaration takes apart, whose defaults keep
947    /// their literal types as the context of the literal's properties.
948    pub const_pattern_sources: std::collections::BTreeSet<ExprId>,
949    /// Pattern decomposition statements, run immediately after their parameter initializes.
950    pub parameter_bindings: std::collections::HashMap<Span, Vec<StmtId>>,
951    /// Source names in lowered for-of heads, whose TDZ includes the iterable.
952    pub for_of_pattern_bindings: std::collections::BTreeMap<StmtId, Vec<Ident>>,
953    /// The `let` names a destructuring `for` initializer declared, by the
954    /// lowered `for`, whose initializer now precedes it (populated by
955    /// `lower_patterns`).
956    pub for_pattern_init_bindings: std::collections::BTreeMap<StmtId, Vec<Ident>>,
957    /// Top-level declarations carrying a leading `export` (Form 1).
958    pub exported_decls: Vec<ExportedDecl>,
959    /// The `undefined` a typed `let x: T;` starts with, synthesized by the parser.
960    pub implicit_initializers: std::collections::BTreeSet<ExprId>,
961    /// Every `undefined` the compiler writes itself, such as an implicit
962    /// initializer. Each reads the value `undefined`, even where a binding
963    /// named `undefined` is in scope.
964    pub synthetic_undefined: std::collections::BTreeSet<ExprId>,
965    /// Destructuring defaults, keyed by the ternary `lower_patterns` makes of each.
966    pub binding_defaults: std::collections::BTreeMap<ExprId, BindingDefault>,
967    /// A top-level `function main` the parser reported and dropped, so the
968    /// program is not also told it has no `main`.
969    pub unparsed_main: bool,
970    /// What lifting block-local type declarations to module scope recorded
971    /// (`lower_local_types`); `None` before it runs. Inference registers named
972    /// types from the top level only, so it rejects an AST that declares a
973    /// type in a block without this.
974    pub lowered_local_types: Option<LoweredLocalTypes>,
975}
976
977/// What lifting block-local type declarations to module scope recorded.
978#[derive(Default, Clone, Debug)]
979pub struct LoweredLocalTypes {
980    /// Errors for local types that can't be lifted or clash with another name,
981    /// reported by inference.
982    pub diagnostics: Vec<crate::Diagnostic>,
983    /// The block, or a switch's arms from its first label, each lifted type is
984    /// visible in, by its key.
985    pub scopes: std::collections::BTreeMap<String, Span>,
986}
987
988impl Ast {
989    pub(crate) fn source_expressions(&self) -> &[Expr] {
990        &self.exprs
991    }
992    pub(crate) fn source_statements(&self) -> &[Stmt] {
993        &self.stmts
994    }
995
996    pub fn validate_source(
997        &self,
998        source: &str,
999        file: crate::FileId,
1000    ) -> Result<(), crate::source::SourceError> {
1001        crate::source_validation::validate(self, source, file)
1002    }
1003
1004    pub fn new() -> Self {
1005        Self::default()
1006    }
1007
1008    /// Checked allocation; failure leaves the arena unchanged.
1009    pub fn try_push_expr(&mut self, expr: Expr) -> Result<ExprId, ArenaError> {
1010        arena::push(&mut self.exprs, expr, ArenaKind::Expressions).map(ExprId)
1011    }
1012
1013    /// Checked allocation; failure leaves the arena unchanged.
1014    pub fn try_push_stmt(&mut self, stmt: Stmt) -> Result<StmtId, ArenaError> {
1015        arena::push(&mut self.stmts, stmt, ArenaKind::Statements).map(StmtId)
1016    }
1017
1018    pub fn try_expr(&self, id: ExprId) -> Result<&Expr, ArenaError> {
1019        arena::get(&self.exprs, id.0, ArenaKind::Expressions)
1020    }
1021
1022    /// Every expression whose span lies within `span`, nested function bodies
1023    /// included.
1024    pub fn exprs_within(&self, span: Span) -> impl Iterator<Item = &Expr> {
1025        self.exprs
1026            .iter()
1027            .filter(move |expr| span.encloses(expr.span))
1028    }
1029
1030    /// The expression `id` is through any parentheses around it.
1031    pub fn try_peel_parens(&self, mut id: ExprId) -> Result<ExprId, ArenaError> {
1032        while let ExprKind::Paren(inner) = &self.try_expr(id)?.kind {
1033            id = *inner;
1034        }
1035        Ok(id)
1036    }
1037
1038    pub fn try_stmt(&self, id: StmtId) -> Result<&Stmt, ArenaError> {
1039        arena::get(&self.stmts, id.0, ArenaKind::Statements)
1040    }
1041
1042    pub fn try_expr_mut(&mut self, id: ExprId) -> Result<&mut Expr, ArenaError> {
1043        arena::get_mut(&mut self.exprs, id.0, ArenaKind::Expressions)
1044    }
1045
1046    pub fn try_stmt_mut(&mut self, id: StmtId) -> Result<&mut Stmt, ArenaError> {
1047        arena::get_mut(&mut self.stmts, id.0, ArenaKind::Statements)
1048    }
1049
1050    /// Snapshot of allocated expression IDs, usable while appending new nodes.
1051    pub fn expr_ids(&self) -> Result<impl DoubleEndedIterator<Item = ExprId> + use<>, ArenaError> {
1052        Ok(arena::ids(self.exprs.len(), ArenaKind::Expressions)?.map(ExprId))
1053    }
1054
1055    /// Snapshot of allocated statement IDs, usable while appending new nodes.
1056    pub fn stmt_ids(&self) -> Result<impl DoubleEndedIterator<Item = StmtId> + use<>, ArenaError> {
1057        Ok(arena::ids(self.stmts.len(), ArenaKind::Statements)?.map(StmtId))
1058    }
1059
1060    pub fn exprs_len(&self) -> usize {
1061        self.exprs.len()
1062    }
1063
1064    pub fn stmts_len(&self) -> usize {
1065        self.stmts.len()
1066    }
1067}
1068
1069#[cfg(test)]
1070mod tests {
1071    use super::{Ast, BinOp, Expr, ExprKind, Stmt, StmtKind, UnOp};
1072    use crate::Span;
1073
1074    #[test]
1075    fn arena_round_trip_for_exprs() {
1076        let mut ast = Ast::new();
1077        let id = ast
1078            .try_push_expr(Expr {
1079                kind: ExprKind::Number(42.0),
1080                span: Span::new(crate::FileId(0), 0, 2).unwrap(),
1081            })
1082            .unwrap();
1083        assert_eq!(id.0, 0);
1084        assert_eq!(ast.try_expr(id).unwrap().kind, ExprKind::Number(42.0));
1085        assert_eq!(
1086            ast.try_expr(id).unwrap().span,
1087            Span::new(crate::FileId(0), 0, 2).unwrap()
1088        );
1089    }
1090
1091    #[test]
1092    fn arena_round_trip_for_stmts() {
1093        let mut ast = Ast::new();
1094        let expr_id = ast
1095            .try_push_expr(Expr {
1096                kind: ExprKind::Null,
1097                span: Span::new(crate::FileId(0), 0, 4).unwrap(),
1098            })
1099            .unwrap();
1100        let stmt_id = ast
1101            .try_push_stmt(Stmt {
1102                kind: StmtKind::Expr(expr_id),
1103                span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1104            })
1105            .unwrap();
1106        assert_eq!(stmt_id.0, 0);
1107        assert_eq!(
1108            ast.try_stmt(stmt_id).unwrap().span,
1109            Span::new(crate::FileId(0), 0, 5).unwrap()
1110        );
1111        assert!(matches!(
1112            ast.try_stmt(stmt_id).unwrap().kind,
1113            StmtKind::Expr(_)
1114        ));
1115    }
1116
1117    #[test]
1118    fn build_binary_expression_tree() {
1119        let mut ast = Ast::new();
1120        let lhs = ast
1121            .try_push_expr(Expr {
1122                kind: ExprKind::Number(1.0),
1123                span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1124            })
1125            .unwrap();
1126        let rhs = ast
1127            .try_push_expr(Expr {
1128                kind: ExprKind::Number(2.0),
1129                span: Span::new(crate::FileId(0), 4, 5).unwrap(),
1130            })
1131            .unwrap();
1132        let sum = ast
1133            .try_push_expr(Expr {
1134                kind: ExprKind::Binary {
1135                    op: BinOp::Add,
1136                    lhs,
1137                    rhs,
1138                },
1139                span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1140            })
1141            .unwrap();
1142        let stmt_id = ast
1143            .try_push_stmt(Stmt {
1144                kind: StmtKind::Expr(sum),
1145                span: Span::new(crate::FileId(0), 0, 6).unwrap(),
1146            })
1147            .unwrap();
1148        ast.top_level.push(stmt_id);
1149
1150        let top = ast.try_stmt(stmt_id).unwrap();
1151        let StmtKind::Expr(sum_id) = top.kind else {
1152            panic!("expected expression statement")
1153        };
1154        let sum_expr = ast.try_expr(sum_id).unwrap();
1155        let ExprKind::Binary { op, lhs, rhs } = sum_expr.kind else {
1156            panic!("expected binary expression")
1157        };
1158        assert_eq!(op, BinOp::Add);
1159        assert_eq!(ast.try_expr(lhs).unwrap().kind, ExprKind::Number(1.0));
1160        assert_eq!(ast.try_expr(rhs).unwrap().kind, ExprKind::Number(2.0));
1161    }
1162
1163    #[test]
1164    fn clone_and_equality() {
1165        let kind = ExprKind::Unary {
1166            op: UnOp::Neg,
1167            operand: super::ExprId(0),
1168        };
1169        assert_eq!(kind.clone(), kind);
1170        assert_eq!(BinOp::Eq, BinOp::Eq);
1171        assert_ne!(BinOp::Eq, BinOp::NotEq);
1172    }
1173
1174    #[test]
1175    fn new_ast_is_empty() {
1176        let ast = Ast::new();
1177        assert!(ast.top_level.is_empty());
1178    }
1179}
1180
1181/// A string index signature; the parameter name is documentation only.
1182#[derive(Clone, Debug, PartialEq)]
1183pub struct IndexSignatureAnnotation {
1184    pub key: TypeAnnotation,
1185    pub value: TypeAnnotation,
1186    pub readonly: bool,
1187    pub span: Span,
1188}