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        /// 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<Ident>,
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<Ident>,
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<Ident>,
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 ImportSpecifier {
538    pub imported_name: Ident,
539    pub local_name: Ident,
540}
541
542#[derive(Clone, Debug, PartialEq)]
543pub enum InterfaceMember {
544    IndexSignature(IndexSignatureAnnotation),
545    Method {
546        name: Ident,
547        optional: bool,
548        generics: Vec<Ident>,
549        params: Vec<ParamDecl>,
550        return_type: TypeAnnotation,
551        span: Span,
552        doc: Option<DocComment>,
553    },
554    /// Property. `readonly` forbids writes through the interface; `optional: true`
555    /// widens reads to `T | undefined`.
556    Property {
557        name: Ident,
558        ty: TypeAnnotation,
559        optional: bool,
560        readonly: bool,
561        span: Span,
562        doc: Option<DocComment>,
563    },
564}
565
566/// Member visibility. No `protected` — it is rejected at parse time.
567#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
568pub enum Visibility {
569    #[default]
570    Public,
571    Private,
572}
573
574impl Visibility {
575    pub fn is_public(&self) -> bool {
576        *self == Visibility::Public
577    }
578}
579
580/// Modifiers on a class field or method. `readonly`/`visibility_span` carry spans so
581/// later phases can anchor diagnostics (e.g. assignment to a `readonly` field).
582#[derive(Clone, Debug, PartialEq)]
583pub struct ClassModifiers {
584    pub visibility: Visibility,
585    pub visibility_span: Option<Span>,
586    pub readonly: Option<Span>,
587    pub static_span: Option<Span>,
588}
589
590#[derive(Clone, Debug, PartialEq)]
591pub enum ClassMember {
592    Field {
593        name: Ident,
594        modifiers: ClassModifiers,
595        /// `x?: T`.
596        optional: bool,
597        /// `f?(): T;`: an optional method declared without a body, held as a
598        /// field of its function type that a subclass method may fill.
599        method_signature: bool,
600        ty: TypeAnnotation,
601        /// `= expr`. Semantics owned by the typechecker.
602        initializer: Option<ExprId>,
603        span: Span,
604        doc: Option<DocComment>,
605    },
606    Method {
607        name: Ident,
608        optional: bool,
609        modifiers: ClassModifiers,
610        generics: Vec<Ident>,
611        params: Vec<ParamDecl>,
612        return_type: TypeAnnotation,
613        body: StmtId,
614        span: Span,
615        doc: Option<DocComment>,
616    },
617    Constructor {
618        /// `private constructor`: only the class's own module may call it or
619        /// extend the class.
620        visibility: Visibility,
621        params: Vec<ParamDecl>,
622        body: StmtId,
623        span: Span,
624        doc: Option<DocComment>,
625    },
626    /// `get x(): T { … }` or `set x(v: T) { … }`. Get/set of the same name pair
627    /// into one property; the typechecker validates and pairs them.
628    Accessor {
629        name: Ident,
630        modifiers: ClassModifiers,
631        kind: AccessorKind,
632        /// The setter's single parameter; `None` for a getter. Boxed to keep the
633        /// variant near its siblings' size (clippy `large_enum_variant`).
634        param: Option<Box<ParamDecl>>,
635        /// The getter's return type; `None` for a setter.
636        return_type: Option<TypeAnnotation>,
637        body: StmtId,
638        span: Span,
639        doc: Option<DocComment>,
640    },
641}
642
643#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
644pub enum AccessorKind {
645    Get,
646    Set,
647}
648
649#[derive(Clone, Debug, PartialEq)]
650pub struct EnumMember {
651    pub name: Ident,
652    pub value: Option<EnumInitializer>,
653    pub span: Span,
654    pub doc: Option<DocComment>,
655}
656
657/// Enum member initializer. Restricted to numeric and string literals (numeric may be negated).
658#[derive(Clone, Debug, PartialEq)]
659pub enum EnumInitializer {
660    /// `= 1` or `= -1` — `span` covers the leading minus when present.
661    Number {
662        value: f64,
663        span: Span,
664    },
665    String {
666        value: String,
667        span: Span,
668    },
669}
670
671impl EnumInitializer {
672    pub fn span(&self) -> Span {
673        match self {
674            EnumInitializer::Number { span, .. } => *span,
675            EnumInitializer::String { span, .. } => *span,
676        }
677    }
678}
679
680/// `values` has multiple entries when consecutive `case` labels share a body.
681#[derive(Clone, Debug, PartialEq)]
682pub struct SwitchCase {
683    pub values: Vec<ExprId>,
684    pub body: StmtId,
685    pub span: Span,
686}
687
688/// Separate from `SwitchCase` so duplicate-default detection has nowhere to slip.
689#[derive(Clone, Debug, PartialEq)]
690pub struct SwitchDefault {
691    pub body: StmtId,
692    pub span: Span,
693}
694
695/// `catch (e)` and bindingless `catch` are catch-all clauses.
696/// A bindingless clause uses an inaccessible compiler identifier.
697/// When present, the typechecker verifies `ty` is an Error class.
698#[derive(Clone, Debug, PartialEq)]
699pub struct CatchClause {
700    pub binding: Ident,
701    pub ty: Option<TypeAnnotation>,
702    pub body: StmtId,
703    pub span: Span,
704}
705
706/// Source-syntax parameter. Fully-resolved form is [`crate::Param`].
707#[derive(Clone, Debug, PartialEq)]
708pub struct ParamDecl {
709    pub name: Ident,
710    pub ty: Option<TypeAnnotation>,
711    pub default: Option<ExprId>,
712    /// Declared with `?`. A parameter with a default may be omitted too;
713    /// [`ParamDecl::is_omittable`] asks that.
714    pub optional: bool,
715    /// Cleared by `lower_patterns`; always `None` post-lowering.
716    pub pattern: Option<Binding>,
717    pub rest: bool,
718    /// `Some` for a constructor parameter property (`constructor(public x: T)`):
719    /// declares and auto-assigns a field. `None` for an ordinary parameter.
720    pub modifiers: Option<ClassModifiers>,
721}
722
723impl ParamDecl {
724    /// Whether a caller may leave the argument out: declared `?`, or defaulted.
725    pub fn is_omittable(&self) -> bool {
726        self.optional || self.default.is_some()
727    }
728}
729
730/// Destructuring binding pattern. Plain `Ident` bindings stay as `Let`/`Const` stmts; only `{…}` / `[…]` forms appear here.
731#[derive(Clone, Debug, PartialEq)]
732pub enum Binding {
733    Object {
734        fields: Vec<ObjectPatternField>,
735        rest: Option<Ident>,
736        span: Span,
737    },
738    Array {
739        /// `None` entries are holes (`[, x]`).
740        elems: Vec<Option<Ident>>,
741        /// Defaults at the corresponding element positions; holes have no default.
742        defaults: Vec<Option<ExprId>>,
743        rest: Option<Ident>,
744        span: Span,
745    },
746}
747
748impl Binding {
749    pub fn span(&self) -> Span {
750        match self {
751            Binding::Object { span, .. } | Binding::Array { span, .. } => *span,
752        }
753    }
754}
755
756/// `source` is the field name on the RHS; `local` is the binding name. Equal for shorthand `{ a }`, differ for `{ a: x }`.
757#[derive(Clone, Debug, PartialEq)]
758pub struct ObjectPatternField {
759    pub source: Ident,
760    pub local: Ident,
761    pub default: Option<ExprId>,
762    pub span: Span,
763}
764
765#[derive(Clone, Debug, PartialEq)]
766pub struct TypeAnnotation {
767    pub kind: TypeAnnotationKind,
768    pub span: Span,
769}
770
771/// Surface form of `<param> is <T>`. Only valid at a function return-type position.
772#[derive(Clone, Debug, PartialEq)]
773pub struct TypePredicateAnnotation {
774    pub param: Ident,
775    pub asserted: TypeAnnotation,
776    pub span: Span,
777}
778
779#[derive(Clone, Debug, PartialEq)]
780pub enum TypeAnnotationKind {
781    /// `name.span` covers the identifier only; outer span includes any `<…>` args.
782    Name {
783        name: Ident,
784        args: Vec<TypeAnnotation>,
785    },
786    /// Dotted type like `Temporal.Instant`. `path.len() >= 2`; last segment is the type name.
787    Qualified {
788        path: Vec<Ident>,
789        args: Vec<TypeAnnotation>,
790    },
791    StringLiteral(String),
792    /// Parser canonicalizes `-0.0` to `0.0`.
793    NumberLiteral(crate::types::LiteralF64),
794    /// Decimal digits with a leading `-` when negative, as in [`crate::Type::BigIntLiteral`].
795    BigIntLiteral(String),
796    BooleanLiteral(bool),
797    Array(Box<TypeAnnotation>),
798    /// Element labels (`[x: number, y: number]`) are documentation only, so the
799    /// parser checks and drops them.
800    Tuple(Vec<TypeAnnotation>),
801    /// An omittable tuple element (`T?` or `name?: T`).
802    Optional(Box<TypeAnnotation>),
803    /// `readonly T[]` / `readonly [A, B]`. The parser only builds this around an
804    /// [`Array`](Self::Array) or [`Tuple`](Self::Tuple) operand.
805    Readonly(Box<TypeAnnotation>),
806    Object {
807        fields: Vec<TypeAnnotationField>,
808        index: Option<Box<IndexSignatureAnnotation>>,
809    },
810    /// Function type annotation. Only the named-parameter form is accepted.
811    Function {
812        params: Vec<TypeAnnotationField>,
813        return_type: Box<TypeAnnotation>,
814    },
815    /// Always ≥2 members; single-element unions are unwrapped by the parser.
816    Union(Vec<TypeAnnotation>),
817    /// `keyof T` — the union of `T`'s member names as string literal types.
818    /// Resolved eagerly, so this never reaches the typed AST.
819    KeyOf(Box<TypeAnnotation>),
820    /// `typeof x` — the type of the *value* `x`, looked up in the value namespace
821    /// rather than the type namespace. `path` is the dotted reference, one span per
822    /// segment (`typeof o.k` has two). Resolved eagerly, like [`KeyOf`](Self::KeyOf).
823    TypeOf {
824        path: Vec<Ident>,
825    },
826}
827
828/// Reused for object-type fields and function-type parameters. `rest` is always
829/// `false` in object position; `readonly` is only meaningful for object-type fields
830/// (always `false` for function/tuple parameters).
831#[derive(Clone, Debug, PartialEq)]
832pub struct TypeAnnotationField {
833    pub name: Ident,
834    pub ty: TypeAnnotation,
835    pub optional: bool,
836    pub readonly: bool,
837    pub rest: bool,
838    /// Declared with method syntax, `m(): T`, rather than as a property `m: () => T`.
839    pub method: bool,
840}
841
842/// A destructuring default, lowered to `saved === undefined ? default : saved`.
843#[derive(Clone, Debug, PartialEq, Eq)]
844pub struct BindingDefault {
845    /// The component read, saved once before the default is chosen.
846    pub saved: Ident,
847    /// The default expression.
848    pub default: ExprId,
849    /// Whether the pattern's root has a type annotation, which the default
850    /// must then fit.
851    pub annotated: bool,
852}
853
854/// Metadata for a synthesized `IndexAccess` from pattern lowering — lets the typechecker emit destructure-specific errors.
855#[derive(Clone, Debug, PartialEq, Eq)]
856pub struct PatternOrigin {
857    pub pattern_span: Span,
858    pub slot_arity: usize,
859    /// The slot has a default, so a read past a tuple's end is `undefined`
860    /// rather than an error, as in tsc; lowering guards the read by length.
861    pub defaulted: bool,
862}
863
864/// A top-level declaration marked `export` (Form 1: `export function …`,
865/// `export const …`, etc.). The declaration itself stays a plain node in
866/// `top_level`; this side record only tracks which ones are export-marked, so
867/// the inference passes need no `export` awareness.
868#[derive(Clone, Debug)]
869pub struct ExportedDecl {
870    pub stmt: StmtId,
871    /// Span of the `export` keyword — used for diagnostics and `ExportEntry`.
872    pub export_span: Span,
873}
874
875#[derive(Default, Clone, Debug)]
876pub struct Ast {
877    exprs: Vec<Expr>,
878    stmts: Vec<Stmt>,
879    pub top_level: Vec<StmtId>,
880    /// Maps synthesized `IndexAccess` IDs → their source pattern (populated by `lower_patterns`).
881    /// `BTreeMap` is precautionary: the compiler only ever looks entries up by `ExprId`, and
882    /// the parser snapshots that render `Ast`'s `Debug` all run before `lower_patterns`
883    /// populates this, so order is unobserved today. An ordered map keeps any future walk —
884    /// or post-lowering snapshot — independent of the hash seed.
885    pub pattern_origins: std::collections::BTreeMap<ExprId, PatternOrigin>,
886    /// Array literals that an array pattern destructures directly, as in
887    /// `let [a, b] = [1, "s"]`. TypeScript types such a literal as a tuple from
888    /// the pattern, so each name gets its element's type and a pattern longer
889    /// than the literal is a compile error (populated by `lower_patterns`).
890    pub tuple_pattern_sources: std::collections::BTreeSet<ExprId>,
891    /// For a parameter's tuple literal default shorter than its pattern, the
892    /// defaults of the slots past its end (`None` for a hole). TypeScript pads
893    /// the default's type with an optional element for each, so a caller may
894    /// pass them.
895    pub tuple_pattern_pad_defaults: std::collections::BTreeMap<ExprId, Vec<Option<ExprId>>>,
896    /// For a parameter's object literal default, the properties its pattern
897    /// gives defaults that the literal lacks, with those defaults:
898    /// `{ a = 1 } = {}`. TypeScript adds each to the default's type as an
899    /// optional property, so a caller may pass it.
900    pub object_pattern_pad_defaults: std::collections::BTreeMap<ExprId, Vec<(String, ExprId)>>,
901    /// Unannotated object literals an object pattern takes apart, with the
902    /// properties the pattern gives defaults and their defaults:
903    /// `{ a, b = 0 } = { a: 1, b: 2 }`. TypeScript types such a literal from
904    /// the pattern, so those properties are optional in its type (populated by
905    /// `lower_patterns`). A default that may read a name the pattern binds is
906    /// `None`: it can't be typed ahead of the pattern as the property's context.
907    pub object_pattern_sources:
908        std::collections::BTreeMap<ExprId, std::collections::BTreeMap<String, Option<ExprId>>>,
909    /// For a tuple pattern source, the defaults of the pattern's slots the
910    /// literal fills, by index: `[a = 1] = [2]`.
911    pub tuple_pattern_defaults:
912        std::collections::BTreeMap<ExprId, std::collections::BTreeMap<usize, ExprId>>,
913    /// Pattern sources a `const` declaration takes apart, whose defaults keep
914    /// their literal types as the context of the literal's properties.
915    pub const_pattern_sources: std::collections::BTreeSet<ExprId>,
916    /// Pattern decomposition statements, run immediately after their parameter initializes.
917    pub parameter_bindings: std::collections::HashMap<Span, Vec<StmtId>>,
918    /// Source names in lowered for-of heads, whose TDZ includes the iterable.
919    pub for_of_pattern_bindings: std::collections::BTreeMap<StmtId, Vec<Ident>>,
920    /// The `let` names a destructuring `for` initializer declared, by the
921    /// lowered `for`, whose initializer now precedes it (populated by
922    /// `lower_patterns`).
923    pub for_pattern_init_bindings: std::collections::BTreeMap<StmtId, Vec<Ident>>,
924    /// Top-level declarations carrying a leading `export` (Form 1).
925    pub exported_decls: Vec<ExportedDecl>,
926    /// The `undefined` a typed `let x: T;` starts with, synthesized by the parser.
927    pub implicit_initializers: std::collections::BTreeSet<ExprId>,
928    /// Every `undefined` the compiler writes itself, such as an implicit
929    /// initializer. Each reads the value `undefined`, even where a binding
930    /// named `undefined` is in scope.
931    pub synthetic_undefined: std::collections::BTreeSet<ExprId>,
932    /// Destructuring defaults, keyed by the ternary `lower_patterns` makes of each.
933    pub binding_defaults: std::collections::BTreeMap<ExprId, BindingDefault>,
934    /// A top-level `function main` the parser reported and dropped, so the
935    /// program is not also told it has no `main`.
936    pub unparsed_main: bool,
937}
938
939impl Ast {
940    pub(crate) fn source_expressions(&self) -> &[Expr] {
941        &self.exprs
942    }
943    pub(crate) fn source_statements(&self) -> &[Stmt] {
944        &self.stmts
945    }
946
947    pub fn validate_source(
948        &self,
949        source: &str,
950        file: crate::FileId,
951    ) -> Result<(), crate::source::SourceError> {
952        crate::source_validation::validate(self, source, file)
953    }
954
955    pub fn new() -> Self {
956        Self::default()
957    }
958
959    /// Checked allocation; failure leaves the arena unchanged.
960    pub fn try_push_expr(&mut self, expr: Expr) -> Result<ExprId, ArenaError> {
961        arena::push(&mut self.exprs, expr, ArenaKind::Expressions).map(ExprId)
962    }
963
964    /// Checked allocation; failure leaves the arena unchanged.
965    pub fn try_push_stmt(&mut self, stmt: Stmt) -> Result<StmtId, ArenaError> {
966        arena::push(&mut self.stmts, stmt, ArenaKind::Statements).map(StmtId)
967    }
968
969    pub fn try_expr(&self, id: ExprId) -> Result<&Expr, ArenaError> {
970        arena::get(&self.exprs, id.0, ArenaKind::Expressions)
971    }
972
973    /// Every expression whose span lies within `span`, nested function bodies
974    /// included.
975    pub fn exprs_within(&self, span: Span) -> impl Iterator<Item = &Expr> {
976        self.exprs
977            .iter()
978            .filter(move |expr| span.encloses(expr.span))
979    }
980
981    /// The expression `id` is through any parentheses around it.
982    pub fn try_peel_parens(&self, mut id: ExprId) -> Result<ExprId, ArenaError> {
983        while let ExprKind::Paren(inner) = &self.try_expr(id)?.kind {
984            id = *inner;
985        }
986        Ok(id)
987    }
988
989    pub fn try_stmt(&self, id: StmtId) -> Result<&Stmt, ArenaError> {
990        arena::get(&self.stmts, id.0, ArenaKind::Statements)
991    }
992
993    pub fn try_expr_mut(&mut self, id: ExprId) -> Result<&mut Expr, ArenaError> {
994        arena::get_mut(&mut self.exprs, id.0, ArenaKind::Expressions)
995    }
996
997    pub fn try_stmt_mut(&mut self, id: StmtId) -> Result<&mut Stmt, ArenaError> {
998        arena::get_mut(&mut self.stmts, id.0, ArenaKind::Statements)
999    }
1000
1001    /// Snapshot of allocated expression IDs, usable while appending new nodes.
1002    pub fn expr_ids(&self) -> Result<impl DoubleEndedIterator<Item = ExprId> + use<>, ArenaError> {
1003        Ok(arena::ids(self.exprs.len(), ArenaKind::Expressions)?.map(ExprId))
1004    }
1005
1006    /// Snapshot of allocated statement IDs, usable while appending new nodes.
1007    pub fn stmt_ids(&self) -> Result<impl DoubleEndedIterator<Item = StmtId> + use<>, ArenaError> {
1008        Ok(arena::ids(self.stmts.len(), ArenaKind::Statements)?.map(StmtId))
1009    }
1010
1011    pub fn exprs_len(&self) -> usize {
1012        self.exprs.len()
1013    }
1014
1015    pub fn stmts_len(&self) -> usize {
1016        self.stmts.len()
1017    }
1018}
1019
1020#[cfg(test)]
1021mod tests {
1022    use super::{Ast, BinOp, Expr, ExprKind, Stmt, StmtKind, UnOp};
1023    use crate::Span;
1024
1025    #[test]
1026    fn arena_round_trip_for_exprs() {
1027        let mut ast = Ast::new();
1028        let id = ast
1029            .try_push_expr(Expr {
1030                kind: ExprKind::Number(42.0),
1031                span: Span::new(crate::FileId(0), 0, 2).unwrap(),
1032            })
1033            .unwrap();
1034        assert_eq!(id.0, 0);
1035        assert_eq!(ast.try_expr(id).unwrap().kind, ExprKind::Number(42.0));
1036        assert_eq!(
1037            ast.try_expr(id).unwrap().span,
1038            Span::new(crate::FileId(0), 0, 2).unwrap()
1039        );
1040    }
1041
1042    #[test]
1043    fn arena_round_trip_for_stmts() {
1044        let mut ast = Ast::new();
1045        let expr_id = ast
1046            .try_push_expr(Expr {
1047                kind: ExprKind::Null,
1048                span: Span::new(crate::FileId(0), 0, 4).unwrap(),
1049            })
1050            .unwrap();
1051        let stmt_id = ast
1052            .try_push_stmt(Stmt {
1053                kind: StmtKind::Expr(expr_id),
1054                span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1055            })
1056            .unwrap();
1057        assert_eq!(stmt_id.0, 0);
1058        assert_eq!(
1059            ast.try_stmt(stmt_id).unwrap().span,
1060            Span::new(crate::FileId(0), 0, 5).unwrap()
1061        );
1062        assert!(matches!(
1063            ast.try_stmt(stmt_id).unwrap().kind,
1064            StmtKind::Expr(_)
1065        ));
1066    }
1067
1068    #[test]
1069    fn build_binary_expression_tree() {
1070        let mut ast = Ast::new();
1071        let lhs = ast
1072            .try_push_expr(Expr {
1073                kind: ExprKind::Number(1.0),
1074                span: Span::new(crate::FileId(0), 0, 1).unwrap(),
1075            })
1076            .unwrap();
1077        let rhs = ast
1078            .try_push_expr(Expr {
1079                kind: ExprKind::Number(2.0),
1080                span: Span::new(crate::FileId(0), 4, 5).unwrap(),
1081            })
1082            .unwrap();
1083        let sum = ast
1084            .try_push_expr(Expr {
1085                kind: ExprKind::Binary {
1086                    op: BinOp::Add,
1087                    lhs,
1088                    rhs,
1089                },
1090                span: Span::new(crate::FileId(0), 0, 5).unwrap(),
1091            })
1092            .unwrap();
1093        let stmt_id = ast
1094            .try_push_stmt(Stmt {
1095                kind: StmtKind::Expr(sum),
1096                span: Span::new(crate::FileId(0), 0, 6).unwrap(),
1097            })
1098            .unwrap();
1099        ast.top_level.push(stmt_id);
1100
1101        let top = ast.try_stmt(stmt_id).unwrap();
1102        let StmtKind::Expr(sum_id) = top.kind else {
1103            panic!("expected expression statement")
1104        };
1105        let sum_expr = ast.try_expr(sum_id).unwrap();
1106        let ExprKind::Binary { op, lhs, rhs } = sum_expr.kind else {
1107            panic!("expected binary expression")
1108        };
1109        assert_eq!(op, BinOp::Add);
1110        assert_eq!(ast.try_expr(lhs).unwrap().kind, ExprKind::Number(1.0));
1111        assert_eq!(ast.try_expr(rhs).unwrap().kind, ExprKind::Number(2.0));
1112    }
1113
1114    #[test]
1115    fn clone_and_equality() {
1116        let kind = ExprKind::Unary {
1117            op: UnOp::Neg,
1118            operand: super::ExprId(0),
1119        };
1120        assert_eq!(kind.clone(), kind);
1121        assert_eq!(BinOp::Eq, BinOp::Eq);
1122        assert_ne!(BinOp::Eq, BinOp::NotEq);
1123    }
1124
1125    #[test]
1126    fn new_ast_is_empty() {
1127        let ast = Ast::new();
1128        assert!(ast.top_level.is_empty());
1129    }
1130}
1131
1132/// A string index signature; the parameter name is documentation only.
1133#[derive(Clone, Debug, PartialEq)]
1134pub struct IndexSignatureAnnotation {
1135    pub value: TypeAnnotation,
1136    pub readonly: bool,
1137    pub span: Span,
1138}