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<Ident>,
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        init: Option<StmtId>,
392        condition: Option<ExprId>,
393        update: Option<StmtId>,
394        /// Always a `Block` — parser requires `{ … }`.
395        body: StmtId,
396    },
397    ForOf {
398        binding_kind: BindingKind,
399        name: Ident,
400        ty: Option<TypeAnnotation>,
401        iter: ExprId,
402        /// Always a `Block` — parser requires `{ … }`.
403        body: StmtId,
404    },
405    /// Destructuring `for-of` head. Eliminated by `lower_patterns`; never reaches the typechecker.
406    ForOfPattern {
407        binding_kind: BindingKind,
408        binding: Binding,
409        ty: Option<TypeAnnotation>,
410        iter: ExprId,
411        /// Always a `Block` — parser requires `{ … }`.
412        body: StmtId,
413    },
414    DoWhile {
415        /// Always a `Block` — parser requires `{ … }`.
416        body: StmtId,
417        condition: ExprId,
418    },
419    /// No fallthrough — typechecker rejects a case body that doesn't end in `break` or `return`.
420    Switch {
421        discriminant: ExprId,
422        cases: Vec<SwitchCase>,
423        default: Option<SwitchDefault>,
424    },
425    Break,
426    Continue,
427    Return(Option<ExprId>),
428    Throw {
429        value: ExprId,
430    },
431    /// `catches` may be empty and `finally` optional, but at least one must appear.
432    /// Multiple `catch` clauses dispatch in declaration order on the thrown
433    /// value's nominal class; the typechecker rejects unreachable arms.
434    Try {
435        body: StmtId,
436        catches: Vec<CatchClause>,
437        finally: Option<StmtId>,
438    },
439    Expr(ExprId),
440    Block(Vec<StmtId>),
441    Assign {
442        target: Ident,
443        value: ExprId,
444    },
445    AssignField {
446        receiver: ExprId,
447        field_name: Ident,
448        value: ExprId,
449    },
450    AssignIndex {
451        receiver: ExprId,
452        index: ExprId,
453        value: ExprId,
454    },
455    /// `target += value;` etc. Lowered to `Assign` with a synthesized `Binary`; `op_span` anchors infer diagnostics.
456    CompoundAssign {
457        target: Ident,
458        op: BinOp,
459        op_span: Span,
460        value: ExprId,
461    },
462    CompoundAssignField {
463        receiver: ExprId,
464        field_name: Ident,
465        op: BinOp,
466        op_span: Span,
467        value: ExprId,
468    },
469    CompoundAssignIndex {
470        receiver: ExprId,
471        index: ExprId,
472        op: BinOp,
473        op_span: Span,
474        value: ExprId,
475    },
476    /// Interface declaration. Single declaration site per name; no reopening.
477    InterfaceDecl {
478        name: Ident,
479        generics: Vec<Ident>,
480        extends: Vec<TypeAnnotation>,
481        members: Vec<InterfaceMember>,
482        doc: Option<DocComment>,
483    },
484    /// Class declaration. Single inheritance via `extends`, structural `implements`.
485    /// `generics` is parsed and stored for forward-compat; generic classes are not yet
486    /// consumed downstream (classes.md non-goals).
487    ClassDecl {
488        name: Ident,
489        generics: Vec<Ident>,
490        extends: Option<TypeAnnotation>,
491        implements: Vec<TypeAnnotation>,
492        members: Vec<ClassMember>,
493        doc: Option<DocComment>,
494    },
495    /// Enum declaration. No const enums, no computed members. `value: None` means auto-numbered at typecheck time.
496    EnumDecl {
497        name: Ident,
498        members: Vec<EnumMember>,
499        doc: Option<DocComment>,
500    },
501    TypeAliasDecl {
502        name: Ident,
503        generics: Vec<Ident>,
504        ty: TypeAnnotation,
505        doc: Option<DocComment>,
506    },
507    /// Top-level only; nested imports are a parse error.
508    Import {
509        module: String,
510        module_span: Span,
511        kind: ImportKind,
512        doc: Option<DocComment>,
513    },
514    /// Re-export (`export { a, b as c } from "./util";` or `export { x };`).
515    /// Top-level only. Parses in single-file but the typechecker gates it
516    /// until cross-module packages land. `source` is the `from "…"` module
517    /// path + span, or `None` for the bare `export { x };` form.
518    ExportFrom {
519        specs: Vec<ImportSpecifier>,
520        source: Option<(String, Span)>,
521        doc: Option<DocComment>,
522    },
523}
524
525#[derive(Clone, Debug, PartialEq)]
526pub enum ImportKind {
527    Named(Vec<ImportSpecifier>),
528    /// Compile-time namespace — namespace value is not first-class (`const x = uuid;` is rejected).
529    Namespace {
530        local_name: Ident,
531    },
532}
533
534#[derive(Clone, Debug, PartialEq)]
535pub struct ImportSpecifier {
536    pub imported_name: Ident,
537    pub local_name: Ident,
538}
539
540#[derive(Clone, Debug, PartialEq)]
541pub enum InterfaceMember {
542    IndexSignature(IndexSignatureAnnotation),
543    Method {
544        name: Ident,
545        optional: bool,
546        generics: Vec<Ident>,
547        params: Vec<ParamDecl>,
548        return_type: TypeAnnotation,
549        span: Span,
550        doc: Option<DocComment>,
551    },
552    /// Property. `readonly` forbids writes through the interface; `optional: true`
553    /// widens reads to `T | undefined`.
554    Property {
555        name: Ident,
556        ty: TypeAnnotation,
557        optional: bool,
558        readonly: bool,
559        span: Span,
560        doc: Option<DocComment>,
561    },
562}
563
564/// Member visibility. No `protected` — it is rejected at parse time.
565#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
566pub enum Visibility {
567    #[default]
568    Public,
569    Private,
570}
571
572impl Visibility {
573    pub fn is_public(&self) -> bool {
574        *self == Visibility::Public
575    }
576}
577
578/// Modifiers on a class field or method. `readonly`/`visibility_span` carry spans so
579/// later phases can anchor diagnostics (e.g. assignment to a `readonly` field).
580#[derive(Clone, Debug, PartialEq)]
581pub struct ClassModifiers {
582    pub visibility: Visibility,
583    pub visibility_span: Option<Span>,
584    pub readonly: Option<Span>,
585    pub static_span: Option<Span>,
586}
587
588#[derive(Clone, Debug, PartialEq)]
589pub enum ClassMember {
590    Field {
591        name: Ident,
592        modifiers: ClassModifiers,
593        /// `x?: T`.
594        optional: bool,
595        ty: TypeAnnotation,
596        /// `= expr`. Semantics owned by the typechecker.
597        initializer: Option<ExprId>,
598        span: Span,
599        doc: Option<DocComment>,
600    },
601    Method {
602        name: Ident,
603        optional: bool,
604        modifiers: ClassModifiers,
605        generics: Vec<Ident>,
606        params: Vec<ParamDecl>,
607        return_type: TypeAnnotation,
608        body: StmtId,
609        span: Span,
610        doc: Option<DocComment>,
611    },
612    Constructor {
613        /// `private constructor`: only the class's own module may call it or
614        /// extend the class.
615        visibility: Visibility,
616        params: Vec<ParamDecl>,
617        body: StmtId,
618        span: Span,
619        doc: Option<DocComment>,
620    },
621    /// `get x(): T { … }` or `set x(v: T) { … }`. Get/set of the same name pair
622    /// into one property; the typechecker validates and pairs them.
623    Accessor {
624        name: Ident,
625        modifiers: ClassModifiers,
626        kind: AccessorKind,
627        /// The setter's single parameter; `None` for a getter. Boxed to keep the
628        /// variant near its siblings' size (clippy `large_enum_variant`).
629        param: Option<Box<ParamDecl>>,
630        /// The getter's return type; `None` for a setter.
631        return_type: Option<TypeAnnotation>,
632        body: StmtId,
633        span: Span,
634        doc: Option<DocComment>,
635    },
636}
637
638#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
639pub enum AccessorKind {
640    Get,
641    Set,
642}
643
644#[derive(Clone, Debug, PartialEq)]
645pub struct EnumMember {
646    pub name: Ident,
647    pub value: Option<EnumInitializer>,
648    pub span: Span,
649    pub doc: Option<DocComment>,
650}
651
652/// Enum member initializer. Restricted to numeric and string literals (numeric may be negated).
653#[derive(Clone, Debug, PartialEq)]
654pub enum EnumInitializer {
655    /// `= 1` or `= -1` — `span` covers the leading minus when present.
656    Number {
657        value: f64,
658        span: Span,
659    },
660    String {
661        value: String,
662        span: Span,
663    },
664}
665
666impl EnumInitializer {
667    pub fn span(&self) -> Span {
668        match self {
669            EnumInitializer::Number { span, .. } => *span,
670            EnumInitializer::String { span, .. } => *span,
671        }
672    }
673}
674
675/// `values` has multiple entries when consecutive `case` labels share a body.
676#[derive(Clone, Debug, PartialEq)]
677pub struct SwitchCase {
678    pub values: Vec<ExprId>,
679    pub body: StmtId,
680    pub span: Span,
681}
682
683/// Separate from `SwitchCase` so duplicate-default detection has nowhere to slip.
684#[derive(Clone, Debug, PartialEq)]
685pub struct SwitchDefault {
686    pub body: StmtId,
687    pub span: Span,
688}
689
690/// `catch (e)` and bindingless `catch` are catch-all clauses.
691/// A bindingless clause uses an inaccessible compiler identifier.
692/// When present, the typechecker verifies `ty` is an Error class.
693#[derive(Clone, Debug, PartialEq)]
694pub struct CatchClause {
695    pub binding: Ident,
696    pub ty: Option<TypeAnnotation>,
697    pub body: StmtId,
698    pub span: Span,
699}
700
701/// Source-syntax parameter. Fully-resolved form is [`crate::Param`].
702#[derive(Clone, Debug, PartialEq)]
703pub struct ParamDecl {
704    pub name: Ident,
705    pub ty: Option<TypeAnnotation>,
706    pub default: Option<ExprId>,
707    /// Declared with `?`. A parameter with a default may be omitted too;
708    /// [`ParamDecl::is_omittable`] asks that.
709    pub optional: bool,
710    /// Cleared by `lower_patterns`; always `None` post-lowering.
711    pub pattern: Option<Binding>,
712    pub rest: bool,
713    /// `Some` for a constructor parameter property (`constructor(public x: T)`):
714    /// declares and auto-assigns a field. `None` for an ordinary parameter.
715    pub modifiers: Option<ClassModifiers>,
716}
717
718impl ParamDecl {
719    /// Whether a caller may leave the argument out: declared `?`, or defaulted.
720    pub fn is_omittable(&self) -> bool {
721        self.optional || self.default.is_some()
722    }
723}
724
725/// Destructuring binding pattern. Plain `Ident` bindings stay as `Let`/`Const` stmts; only `{…}` / `[…]` forms appear here.
726#[derive(Clone, Debug, PartialEq)]
727pub enum Binding {
728    Object {
729        fields: Vec<ObjectPatternField>,
730        rest: Option<Ident>,
731        span: Span,
732    },
733    Array {
734        /// `None` entries are holes (`[, x]`).
735        elems: Vec<Option<Ident>>,
736        /// Defaults at the corresponding element positions; holes have no default.
737        defaults: Vec<Option<ExprId>>,
738        rest: Option<Ident>,
739        span: Span,
740    },
741}
742
743impl Binding {
744    pub fn span(&self) -> Span {
745        match self {
746            Binding::Object { span, .. } | Binding::Array { span, .. } => *span,
747        }
748    }
749}
750
751/// `source` is the field name on the RHS; `local` is the binding name. Equal for shorthand `{ a }`, differ for `{ a: x }`.
752#[derive(Clone, Debug, PartialEq)]
753pub struct ObjectPatternField {
754    pub source: Ident,
755    pub local: Ident,
756    pub default: Option<ExprId>,
757    pub span: Span,
758}
759
760#[derive(Clone, Debug, PartialEq)]
761pub struct TypeAnnotation {
762    pub kind: TypeAnnotationKind,
763    pub span: Span,
764}
765
766/// Surface form of `<param> is <T>`. Only valid at a function return-type position.
767#[derive(Clone, Debug, PartialEq)]
768pub struct TypePredicateAnnotation {
769    pub param: Ident,
770    pub asserted: TypeAnnotation,
771    pub span: Span,
772}
773
774#[derive(Clone, Debug, PartialEq)]
775pub enum TypeAnnotationKind {
776    /// `name.span` covers the identifier only; outer span includes any `<…>` args.
777    Name {
778        name: Ident,
779        args: Vec<TypeAnnotation>,
780    },
781    /// Dotted type like `Temporal.Instant`. `path.len() >= 2`; last segment is the type name.
782    Qualified {
783        path: Vec<Ident>,
784        args: Vec<TypeAnnotation>,
785    },
786    StringLiteral(String),
787    /// Parser canonicalizes `-0.0` to `0.0`.
788    NumberLiteral(crate::types::LiteralF64),
789    /// Decimal digits with a leading `-` when negative, as in [`crate::Type::BigIntLiteral`].
790    BigIntLiteral(String),
791    BooleanLiteral(bool),
792    Array(Box<TypeAnnotation>),
793    /// Element labels (`[x: number, y: number]`) are documentation only, so the
794    /// parser checks and drops them.
795    Tuple(Vec<TypeAnnotation>),
796    /// An omittable tuple element (`T?` or `name?: T`).
797    Optional(Box<TypeAnnotation>),
798    /// `readonly T[]` / `readonly [A, B]`. The parser only builds this around an
799    /// [`Array`](Self::Array) or [`Tuple`](Self::Tuple) operand.
800    Readonly(Box<TypeAnnotation>),
801    Object {
802        fields: Vec<TypeAnnotationField>,
803        index: Option<Box<IndexSignatureAnnotation>>,
804    },
805    /// Function type annotation. Only the named-parameter form is accepted.
806    Function {
807        params: Vec<TypeAnnotationField>,
808        return_type: Box<TypeAnnotation>,
809    },
810    /// Always ≥2 members; single-element unions are unwrapped by the parser.
811    Union(Vec<TypeAnnotation>),
812    /// `keyof T` — the union of `T`'s member names as string literal types.
813    /// Resolved eagerly, so this never reaches the typed AST.
814    KeyOf(Box<TypeAnnotation>),
815    /// `typeof x` — the type of the *value* `x`, looked up in the value namespace
816    /// rather than the type namespace. `path` is the dotted reference, one span per
817    /// segment (`typeof o.k` has two). Resolved eagerly, like [`KeyOf`](Self::KeyOf).
818    TypeOf {
819        path: Vec<Ident>,
820    },
821}
822
823/// Reused for object-type fields and function-type parameters. `rest` is always
824/// `false` in object position; `readonly` is only meaningful for object-type fields
825/// (always `false` for function/tuple parameters).
826#[derive(Clone, Debug, PartialEq)]
827pub struct TypeAnnotationField {
828    pub name: Ident,
829    pub ty: TypeAnnotation,
830    pub optional: bool,
831    pub readonly: bool,
832    pub rest: bool,
833    /// Declared with method syntax, `m(): T`, rather than as a property `m: () => T`.
834    pub method: bool,
835}
836
837/// A destructuring default, lowered to `saved === undefined ? default : saved`.
838#[derive(Clone, Debug, PartialEq, Eq)]
839pub struct BindingDefault {
840    /// The component read, saved once before the default is chosen.
841    pub saved: Ident,
842    /// The default expression.
843    pub default: ExprId,
844    /// Whether the pattern's root has a type annotation, which the default
845    /// must then fit.
846    pub annotated: bool,
847}
848
849/// Metadata for a synthesized `IndexAccess` from pattern lowering — lets the typechecker emit destructure-specific errors.
850#[derive(Clone, Debug, PartialEq, Eq)]
851pub struct PatternOrigin {
852    pub pattern_span: Span,
853    pub slot_arity: usize,
854}
855
856/// A top-level declaration marked `export` (Form 1: `export function …`,
857/// `export const …`, etc.). The declaration itself stays a plain node in
858/// `top_level`; this side record only tracks which ones are export-marked, so
859/// the inference passes need no `export` awareness.
860#[derive(Clone, Debug)]
861pub struct ExportedDecl {
862    pub stmt: StmtId,
863    /// Span of the `export` keyword — used for diagnostics and `ExportEntry`.
864    pub export_span: Span,
865}
866
867#[derive(Default, Clone, Debug)]
868pub struct Ast {
869    exprs: Vec<Expr>,
870    stmts: Vec<Stmt>,
871    pub top_level: Vec<StmtId>,
872    /// Maps synthesized `IndexAccess` IDs → their source pattern (populated by `lower_patterns`).
873    /// `BTreeMap` is precautionary: the compiler only ever looks entries up by `ExprId`, and
874    /// the parser snapshots that render `Ast`'s `Debug` all run before `lower_patterns`
875    /// populates this, so order is unobserved today. An ordered map keeps any future walk —
876    /// or post-lowering snapshot — independent of the hash seed.
877    pub pattern_origins: std::collections::BTreeMap<ExprId, PatternOrigin>,
878    /// Array literals that an array pattern destructures directly, as in
879    /// `let [a, b] = [1, "s"]`. TypeScript types such a literal as a tuple from
880    /// the pattern, so each name gets its element's type and a pattern longer
881    /// than the literal is a compile error (populated by `lower_patterns`).
882    pub tuple_pattern_sources: std::collections::BTreeSet<ExprId>,
883    /// Pattern decomposition statements, run immediately after their parameter initializes.
884    pub parameter_bindings: std::collections::HashMap<Span, Vec<StmtId>>,
885    /// Source names in lowered for-of heads, whose TDZ includes the iterable.
886    pub for_of_pattern_bindings: std::collections::BTreeMap<StmtId, Vec<Ident>>,
887    /// The `let` names a destructuring `for` initializer declared, by the
888    /// lowered `for`, whose initializer now precedes it (populated by
889    /// `lower_patterns`).
890    pub for_pattern_init_bindings: std::collections::BTreeMap<StmtId, Vec<Ident>>,
891    /// Top-level declarations carrying a leading `export` (Form 1).
892    pub exported_decls: Vec<ExportedDecl>,
893    /// The `undefined` a typed `let x: T;` starts with, synthesized by the parser.
894    pub implicit_initializers: std::collections::BTreeSet<ExprId>,
895    /// Every `undefined` the compiler writes itself: implicit initializers and
896    /// the padding of a destructured tuple. Each reads the value `undefined`,
897    /// even where a binding named `undefined` is in scope.
898    pub synthetic_undefined: std::collections::BTreeSet<ExprId>,
899    /// Destructuring defaults, keyed by the ternary `lower_patterns` makes of each.
900    pub binding_defaults: std::collections::BTreeMap<ExprId, BindingDefault>,
901}
902
903impl Ast {
904    pub(crate) fn source_expressions(&self) -> &[Expr] {
905        &self.exprs
906    }
907    pub(crate) fn source_statements(&self) -> &[Stmt] {
908        &self.stmts
909    }
910
911    pub fn validate_source(
912        &self,
913        source: &str,
914        file: crate::FileId,
915    ) -> Result<(), crate::source::SourceError> {
916        crate::source_validation::validate(self, source, file)
917    }
918
919    pub fn new() -> Self {
920        Self::default()
921    }
922
923    /// Checked allocation; failure leaves the arena unchanged.
924    pub fn try_push_expr(&mut self, expr: Expr) -> Result<ExprId, ArenaError> {
925        arena::push(&mut self.exprs, expr, ArenaKind::Expressions).map(ExprId)
926    }
927
928    /// Checked allocation; failure leaves the arena unchanged.
929    pub fn try_push_stmt(&mut self, stmt: Stmt) -> Result<StmtId, ArenaError> {
930        arena::push(&mut self.stmts, stmt, ArenaKind::Statements).map(StmtId)
931    }
932
933    pub fn try_expr(&self, id: ExprId) -> Result<&Expr, ArenaError> {
934        arena::get(&self.exprs, id.0, ArenaKind::Expressions)
935    }
936
937    pub fn try_stmt(&self, id: StmtId) -> Result<&Stmt, ArenaError> {
938        arena::get(&self.stmts, id.0, ArenaKind::Statements)
939    }
940
941    pub fn try_expr_mut(&mut self, id: ExprId) -> Result<&mut Expr, ArenaError> {
942        arena::get_mut(&mut self.exprs, id.0, ArenaKind::Expressions)
943    }
944
945    pub fn try_stmt_mut(&mut self, id: StmtId) -> Result<&mut Stmt, ArenaError> {
946        arena::get_mut(&mut self.stmts, id.0, ArenaKind::Statements)
947    }
948
949    /// Snapshot of allocated expression IDs, usable while appending new nodes.
950    pub fn expr_ids(&self) -> Result<impl DoubleEndedIterator<Item = ExprId> + use<>, ArenaError> {
951        Ok(arena::ids(self.exprs.len(), ArenaKind::Expressions)?.map(ExprId))
952    }
953
954    /// Snapshot of allocated statement IDs, usable while appending new nodes.
955    pub fn stmt_ids(&self) -> Result<impl DoubleEndedIterator<Item = StmtId> + use<>, ArenaError> {
956        Ok(arena::ids(self.stmts.len(), ArenaKind::Statements)?.map(StmtId))
957    }
958
959    pub fn exprs_len(&self) -> usize {
960        self.exprs.len()
961    }
962
963    pub fn stmts_len(&self) -> usize {
964        self.stmts.len()
965    }
966}
967
968#[cfg(test)]
969mod tests {
970    use super::{Ast, BinOp, Expr, ExprKind, Stmt, StmtKind, UnOp};
971    use crate::Span;
972
973    #[test]
974    fn arena_round_trip_for_exprs() {
975        let mut ast = Ast::new();
976        let id = ast
977            .try_push_expr(Expr {
978                kind: ExprKind::Number(42.0),
979                span: Span::new(crate::FileId(0), 0, 2).unwrap(),
980            })
981            .unwrap();
982        assert_eq!(id.0, 0);
983        assert_eq!(ast.try_expr(id).unwrap().kind, ExprKind::Number(42.0));
984        assert_eq!(
985            ast.try_expr(id).unwrap().span,
986            Span::new(crate::FileId(0), 0, 2).unwrap()
987        );
988    }
989
990    #[test]
991    fn arena_round_trip_for_stmts() {
992        let mut ast = Ast::new();
993        let expr_id = ast
994            .try_push_expr(Expr {
995                kind: ExprKind::Null,
996                span: Span::new(crate::FileId(0), 0, 4).unwrap(),
997            })
998            .unwrap();
999        let stmt_id = ast
1000            .try_push_stmt(Stmt {
1001                kind: StmtKind::Expr(expr_id),
1002                span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1003            })
1004            .unwrap();
1005        assert_eq!(stmt_id.0, 0);
1006        assert_eq!(
1007            ast.try_stmt(stmt_id).unwrap().span,
1008            Span::new(crate::FileId(0), 0, 5).unwrap()
1009        );
1010        assert!(matches!(
1011            ast.try_stmt(stmt_id).unwrap().kind,
1012            StmtKind::Expr(_)
1013        ));
1014    }
1015
1016    #[test]
1017    fn build_binary_expression_tree() {
1018        let mut ast = Ast::new();
1019        let lhs = ast
1020            .try_push_expr(Expr {
1021                kind: ExprKind::Number(1.0),
1022                span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1023            })
1024            .unwrap();
1025        let rhs = ast
1026            .try_push_expr(Expr {
1027                kind: ExprKind::Number(2.0),
1028                span: Span::new(crate::FileId(0), 4, 5).unwrap(),
1029            })
1030            .unwrap();
1031        let sum = ast
1032            .try_push_expr(Expr {
1033                kind: ExprKind::Binary {
1034                    op: BinOp::Add,
1035                    lhs,
1036                    rhs,
1037                },
1038                span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1039            })
1040            .unwrap();
1041        let stmt_id = ast
1042            .try_push_stmt(Stmt {
1043                kind: StmtKind::Expr(sum),
1044                span: Span::new(crate::FileId(0), 0, 6).unwrap(),
1045            })
1046            .unwrap();
1047        ast.top_level.push(stmt_id);
1048
1049        let top = ast.try_stmt(stmt_id).unwrap();
1050        let StmtKind::Expr(sum_id) = top.kind else {
1051            panic!("expected expression statement")
1052        };
1053        let sum_expr = ast.try_expr(sum_id).unwrap();
1054        let ExprKind::Binary { op, lhs, rhs } = sum_expr.kind else {
1055            panic!("expected binary expression")
1056        };
1057        assert_eq!(op, BinOp::Add);
1058        assert_eq!(ast.try_expr(lhs).unwrap().kind, ExprKind::Number(1.0));
1059        assert_eq!(ast.try_expr(rhs).unwrap().kind, ExprKind::Number(2.0));
1060    }
1061
1062    #[test]
1063    fn clone_and_equality() {
1064        let kind = ExprKind::Unary {
1065            op: UnOp::Neg,
1066            operand: super::ExprId(0),
1067        };
1068        assert_eq!(kind.clone(), kind);
1069        assert_eq!(BinOp::Eq, BinOp::Eq);
1070        assert_ne!(BinOp::Eq, BinOp::NotEq);
1071    }
1072
1073    #[test]
1074    fn new_ast_is_empty() {
1075        let ast = Ast::new();
1076        assert!(ast.top_level.is_empty());
1077    }
1078}
1079
1080/// A string index signature; the parameter name is documentation only.
1081#[derive(Clone, Debug, PartialEq)]
1082pub struct IndexSignatureAnnotation {
1083    pub value: TypeAnnotation,
1084    pub readonly: bool,
1085    pub span: Span,
1086}