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