Skip to main content

bock_ast/
lib.rs

1//! Bock AST — abstract syntax tree node definitions for the Bock language.
2//!
3//! Every syntactic construct in the Bock grammar maps to a type in this crate.
4//! All nodes carry a [`NodeId`] (for compiler bookkeeping) and a [`Span`]
5//! (for diagnostics and error reporting).
6
7pub use bock_errors::{FileId, Span};
8
9pub mod visitor;
10
11// ─── Node identity ────────────────────────────────────────────────────────────
12
13/// Unique identifier for an AST node within a compilation session.
14pub type NodeId = u32;
15
16// ─── Primitive building blocks ────────────────────────────────────────────────
17
18/// An identifier token with its source span.
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Ident {
21    pub name: String,
22    pub span: Span,
23}
24
25/// A qualified path of identifiers separated by `.`, e.g. `Std.Io.File`.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct TypePath {
28    pub segments: Vec<Ident>,
29    pub span: Span,
30}
31
32/// A module path declared at the top of a file, e.g. `module Std.Io`.
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct ModulePath {
35    pub segments: Vec<Ident>,
36    pub span: Span,
37}
38
39// ─── Visibility ───────────────────────────────────────────────────────────────
40
41/// Declaration visibility.
42#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
43pub enum Visibility {
44    /// Visible only within the current module (default).
45    #[default]
46    Private,
47    /// Visible within the current package/crate.
48    Internal,
49    /// Visible to all consumers.
50    Public,
51}
52
53// ─── Annotations ─────────────────────────────────────────────────────────────
54
55/// A single argument to an annotation, optionally labelled.
56#[derive(Debug, Clone, PartialEq)]
57pub struct AnnotationArg {
58    pub label: Option<Ident>,
59    pub value: Expr,
60}
61
62/// A decorator-style annotation such as `@derive(Equatable)` or
63/// `@performance(max_latency: 100)`.
64#[derive(Debug, Clone, PartialEq)]
65pub struct Annotation {
66    pub id: NodeId,
67    pub span: Span,
68    pub name: Ident,
69    pub args: Vec<AnnotationArg>,
70}
71
72// ─── Generic parameters and constraints ──────────────────────────────────────
73
74/// A single generic type parameter, e.g. `T` or `T: Bound`.
75#[derive(Debug, Clone, PartialEq)]
76pub struct GenericParam {
77    pub id: NodeId,
78    pub span: Span,
79    pub name: Ident,
80    pub bounds: Vec<TypePath>,
81}
82
83/// A `where` clause constraint, e.g. `T: Equatable`.
84#[derive(Debug, Clone, PartialEq)]
85pub struct TypeConstraint {
86    pub id: NodeId,
87    pub span: Span,
88    pub param: Ident,
89    pub bounds: Vec<TypePath>,
90}
91
92// ─── Type expressions ─────────────────────────────────────────────────────────
93
94/// A syntactic type expression as it appears in source code.
95#[derive(Debug, Clone, PartialEq)]
96pub enum TypeExpr {
97    /// A named type, possibly with generic arguments: `List[Int]`.
98    Named {
99        id: NodeId,
100        span: Span,
101        path: TypePath,
102        args: Vec<TypeExpr>,
103    },
104    /// A tuple type: `(Int, String)`.
105    Tuple {
106        id: NodeId,
107        span: Span,
108        elems: Vec<TypeExpr>,
109    },
110    /// A function type: `Fn(Int) -> String` or `Fn(String) -> Void with Log`.
111    Function {
112        id: NodeId,
113        span: Span,
114        params: Vec<TypeExpr>,
115        ret: Box<TypeExpr>,
116        /// Effects listed in the `with` clause (empty if none).
117        effects: Vec<TypePath>,
118    },
119    /// An optional type: `Int?`.
120    Optional {
121        id: NodeId,
122        span: Span,
123        inner: Box<TypeExpr>,
124    },
125    /// The `self` type in trait/impl context.
126    SelfType { id: NodeId, span: Span },
127}
128
129impl TypeExpr {
130    /// Returns this node's [`NodeId`].
131    #[must_use]
132    pub fn node_id(&self) -> NodeId {
133        match self {
134            TypeExpr::Named { id, .. }
135            | TypeExpr::Tuple { id, .. }
136            | TypeExpr::Function { id, .. }
137            | TypeExpr::Optional { id, .. }
138            | TypeExpr::SelfType { id, .. } => *id,
139        }
140    }
141
142    /// Returns this node's source [`Span`].
143    #[must_use]
144    pub fn span(&self) -> Span {
145        match self {
146            TypeExpr::Named { span, .. }
147            | TypeExpr::Tuple { span, .. }
148            | TypeExpr::Function { span, .. }
149            | TypeExpr::Optional { span, .. }
150            | TypeExpr::SelfType { span, .. } => *span,
151        }
152    }
153}
154
155// ─── Literals ────────────────────────────────────────────────────────────────
156
157/// A literal value as it appears in source.
158#[derive(Debug, Clone, PartialEq)]
159pub enum Literal {
160    /// Integer literal, stored as the raw token text to preserve radix/suffix.
161    Int(String),
162    /// Floating-point literal.
163    Float(String),
164    /// Boolean literal.
165    Bool(bool),
166    /// Character literal (a single Unicode scalar).
167    Char(String),
168    /// String literal (already decoded from escape sequences).
169    String(String),
170    /// The unit value `()`.
171    Unit,
172}
173
174/// Known numeric type suffixes (without the leading underscore).
175const TYPE_SUFFIXES: &[&str] = &[
176    "i128", "i64", "i32", "i16", "i8", "u64", "u32", "u16", "u8", "f64", "f32",
177];
178
179/// Strip a type suffix (e.g., `_u8`, `_f64`) from a numeric literal string.
180///
181/// Returns `(numeric_part, Some(suffix_without_underscore))` if a known suffix
182/// is present, or `(original, None)` otherwise.
183#[must_use]
184pub fn strip_type_suffix(s: &str) -> (&str, Option<&str>) {
185    for suffix in TYPE_SUFFIXES {
186        // Check for `_` + suffix at the end of the string.
187        let with_underscore_len = 1 + suffix.len();
188        if s.len() > with_underscore_len {
189            let split = s.len() - with_underscore_len;
190            if s.as_bytes()[split] == b'_' && &s[split + 1..] == *suffix {
191                return (&s[..split], Some(suffix));
192            }
193        }
194    }
195    (s, None)
196}
197
198// ─── Patterns ─────────────────────────────────────────────────────────────────
199
200/// A destructuring pattern used in `let`, `match`, `for`, etc.
201#[derive(Debug, Clone, PartialEq)]
202pub enum Pattern {
203    /// `_` — matches anything, binds nothing.
204    Wildcard { id: NodeId, span: Span },
205    /// `name` — matches anything and binds it immutably.
206    Bind { id: NodeId, span: Span, name: Ident },
207    /// `mut name` — matches anything and binds it mutably.
208    MutBind { id: NodeId, span: Span, name: Ident },
209    /// A literal pattern: `42`, `"hello"`, `true`.
210    Literal {
211        id: NodeId,
212        span: Span,
213        lit: Literal,
214    },
215    /// An enum constructor pattern: `Some(x)`, `Ok(v)`.
216    Constructor {
217        id: NodeId,
218        span: Span,
219        path: TypePath,
220        fields: Vec<Pattern>,
221    },
222    /// A record pattern: `User { name, age }` or `User { name: n, .. }`.
223    Record {
224        id: NodeId,
225        span: Span,
226        path: TypePath,
227        fields: Vec<RecordPatternField>,
228        /// `true` when the pattern contains a `..` rest marker (ignore remaining fields).
229        rest: bool,
230    },
231    /// A tuple pattern: `(a, b, c)`.
232    Tuple {
233        id: NodeId,
234        span: Span,
235        elems: Vec<Pattern>,
236    },
237    /// A list pattern: `[head, ..tail]`.
238    List {
239        id: NodeId,
240        span: Span,
241        elems: Vec<Pattern>,
242        rest: Option<Box<Pattern>>,
243    },
244    /// An or-pattern: `A | B`.
245    Or {
246        id: NodeId,
247        span: Span,
248        alternatives: Vec<Pattern>,
249    },
250    /// A range pattern: `1..10` or `1..=10`.
251    Range {
252        id: NodeId,
253        span: Span,
254        lo: Box<Pattern>,
255        hi: Box<Pattern>,
256        inclusive: bool,
257    },
258    /// A rest pattern `..` (used inside list/tuple patterns).
259    Rest { id: NodeId, span: Span },
260}
261
262impl Pattern {
263    /// Returns this node's [`NodeId`].
264    #[must_use]
265    pub fn node_id(&self) -> NodeId {
266        match self {
267            Pattern::Wildcard { id, .. }
268            | Pattern::Bind { id, .. }
269            | Pattern::MutBind { id, .. }
270            | Pattern::Literal { id, .. }
271            | Pattern::Constructor { id, .. }
272            | Pattern::Record { id, .. }
273            | Pattern::Tuple { id, .. }
274            | Pattern::List { id, .. }
275            | Pattern::Or { id, .. }
276            | Pattern::Range { id, .. }
277            | Pattern::Rest { id, .. } => *id,
278        }
279    }
280
281    /// Returns this node's source [`Span`].
282    #[must_use]
283    pub fn span(&self) -> Span {
284        match self {
285            Pattern::Wildcard { span, .. }
286            | Pattern::Bind { span, .. }
287            | Pattern::MutBind { span, .. }
288            | Pattern::Literal { span, .. }
289            | Pattern::Constructor { span, .. }
290            | Pattern::Record { span, .. }
291            | Pattern::Tuple { span, .. }
292            | Pattern::List { span, .. }
293            | Pattern::Or { span, .. }
294            | Pattern::Range { span, .. }
295            | Pattern::Rest { span, .. } => *span,
296        }
297    }
298}
299
300/// One field binding inside a record pattern.
301#[derive(Debug, Clone, PartialEq)]
302pub struct RecordPatternField {
303    pub span: Span,
304    pub name: Ident,
305    /// `None` means shorthand: `{ name }` ≡ `{ name: name }`.
306    pub pattern: Option<Pattern>,
307}
308
309// ─── Expressions ─────────────────────────────────────────────────────────────
310
311/// A binary operator.
312#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
313pub enum BinOp {
314    // Arithmetic
315    Add,
316    Sub,
317    Mul,
318    Div,
319    Rem,
320    Pow,
321    // Comparison
322    Eq,
323    Ne,
324    Lt,
325    Le,
326    Gt,
327    Ge,
328    // Logical
329    And,
330    Or,
331    // Bitwise
332    BitAnd,
333    BitOr,
334    BitXor,
335    // Function composition
336    Compose, // >>
337    // Type membership
338    Is,
339}
340
341/// A unary operator.
342#[derive(Debug, Clone, Copy, PartialEq, Eq)]
343pub enum UnaryOp {
344    Neg,
345    Not,
346    /// Bitwise NOT: `~x`.
347    BitNot,
348}
349
350/// An assignment operator.
351#[derive(Debug, Clone, Copy, PartialEq, Eq)]
352pub enum AssignOp {
353    Assign,
354    AddAssign,
355    SubAssign,
356    MulAssign,
357    DivAssign,
358    RemAssign,
359}
360
361/// A named argument in a function call: `label: value`.
362#[derive(Debug, Clone, PartialEq)]
363pub struct Arg {
364    pub span: Span,
365    pub label: Option<Ident>,
366    /// Whether the argument is prefixed with `mut` at the call site.
367    pub mutable: bool,
368    pub value: Expr,
369}
370
371/// One field in a record construction expression.
372#[derive(Debug, Clone, PartialEq)]
373pub struct RecordField {
374    pub span: Span,
375    pub name: Ident,
376    /// `None` means shorthand: `{ name }` ≡ `{ name: name }`.
377    pub value: Option<Expr>,
378}
379
380/// A spread element in record construction: `..defaults`.
381#[derive(Debug, Clone, PartialEq)]
382pub struct RecordSpread {
383    pub span: Span,
384    pub expr: Expr,
385}
386
387/// One arm of a `match` expression.
388#[derive(Debug, Clone, PartialEq)]
389pub struct MatchArm {
390    pub id: NodeId,
391    pub span: Span,
392    pub pattern: Pattern,
393    pub guard: Option<Expr>,
394    pub body: Expr,
395}
396
397/// A binding in a `forall` clause of a `property` test.
398#[derive(Debug, Clone, PartialEq)]
399pub struct PropertyBinding {
400    pub span: Span,
401    pub name: Ident,
402    pub ty: TypeExpr,
403}
404
405/// A handler pair inside a `handling` block: `Log with handler`.
406#[derive(Debug, Clone, PartialEq)]
407pub struct HandlerPair {
408    pub span: Span,
409    pub effect: TypePath,
410    pub handler: Expr,
411}
412
413/// An expression node.
414#[derive(Debug, Clone, PartialEq)]
415pub enum Expr {
416    /// A literal value.
417    Literal {
418        id: NodeId,
419        span: Span,
420        lit: Literal,
421    },
422
423    /// An identifier reference.
424    Identifier { id: NodeId, span: Span, name: Ident },
425
426    /// A binary operation: `a + b`.
427    Binary {
428        id: NodeId,
429        span: Span,
430        op: BinOp,
431        left: Box<Expr>,
432        right: Box<Expr>,
433    },
434
435    /// A unary operation: `-x`, `!flag`.
436    Unary {
437        id: NodeId,
438        span: Span,
439        op: UnaryOp,
440        operand: Box<Expr>,
441    },
442
443    /// An assignment expression: `x = 5`, `x += 1`.
444    Assign {
445        id: NodeId,
446        span: Span,
447        op: AssignOp,
448        target: Box<Expr>,
449        value: Box<Expr>,
450    },
451
452    /// A function call: `f(a, b)`.
453    Call {
454        id: NodeId,
455        span: Span,
456        callee: Box<Expr>,
457        args: Vec<Arg>,
458        type_args: Vec<TypeExpr>,
459    },
460
461    /// A method call: `obj.method(a, b)`.
462    MethodCall {
463        id: NodeId,
464        span: Span,
465        receiver: Box<Expr>,
466        method: Ident,
467        type_args: Vec<TypeExpr>,
468        args: Vec<Arg>,
469    },
470
471    /// Field access: `obj.field`.
472    FieldAccess {
473        id: NodeId,
474        span: Span,
475        object: Box<Expr>,
476        field: Ident,
477    },
478
479    /// Index access: `arr[i]`.
480    Index {
481        id: NodeId,
482        span: Span,
483        object: Box<Expr>,
484        index: Box<Expr>,
485    },
486
487    /// Error propagation: `expr?`.
488    Try {
489        id: NodeId,
490        span: Span,
491        expr: Box<Expr>,
492    },
493
494    /// A lambda: `(x) => x * 2`.
495    Lambda {
496        id: NodeId,
497        span: Span,
498        params: Vec<Param>,
499        body: Box<Expr>,
500    },
501
502    /// Pipe operator: `data |> parse`.
503    Pipe {
504        id: NodeId,
505        span: Span,
506        left: Box<Expr>,
507        right: Box<Expr>,
508    },
509
510    /// Function composition: `parse >> validate`.
511    Compose {
512        id: NodeId,
513        span: Span,
514        left: Box<Expr>,
515        right: Box<Expr>,
516    },
517
518    /// An `if` / `if-let` expression.
519    If {
520        id: NodeId,
521        span: Span,
522        /// For `if-let`, this holds the pattern; `None` for plain `if`.
523        let_pattern: Option<Pattern>,
524        condition: Box<Expr>,
525        then_block: Block,
526        else_block: Option<Box<Expr>>,
527    },
528
529    /// A `match` expression.
530    Match {
531        id: NodeId,
532        span: Span,
533        scrutinee: Box<Expr>,
534        arms: Vec<MatchArm>,
535    },
536
537    /// A `loop` expression: `loop { ... break value }`.
538    Loop { id: NodeId, span: Span, body: Block },
539
540    /// A block expression: `{ stmts... }`.
541    Block {
542        id: NodeId,
543        span: Span,
544        block: Block,
545    },
546
547    /// Record construction: `User { id: 1, name, ..defaults }`.
548    RecordConstruct {
549        id: NodeId,
550        span: Span,
551        path: TypePath,
552        fields: Vec<RecordField>,
553        spread: Option<Box<RecordSpread>>,
554    },
555
556    /// List literal: `[1, 2, 3]`.
557    ListLiteral {
558        id: NodeId,
559        span: Span,
560        elems: Vec<Expr>,
561    },
562
563    /// Map literal: `{"key": value}`.
564    MapLiteral {
565        id: NodeId,
566        span: Span,
567        entries: Vec<(Expr, Expr)>,
568    },
569
570    /// Set literal: `#{"a", "b"}`.
571    SetLiteral {
572        id: NodeId,
573        span: Span,
574        elems: Vec<Expr>,
575    },
576
577    /// Tuple literal: `("hello", 42)`.
578    TupleLiteral {
579        id: NodeId,
580        span: Span,
581        elems: Vec<Expr>,
582    },
583
584    /// A range: `1..10` (exclusive) or `1..=10` (inclusive).
585    Range {
586        id: NodeId,
587        span: Span,
588        lo: Box<Expr>,
589        hi: Box<Expr>,
590        inclusive: bool,
591    },
592
593    /// An `await` expression: `expr.await` or `await expr`.
594    Await {
595        id: NodeId,
596        span: Span,
597        expr: Box<Expr>,
598    },
599
600    /// A `return` expression.
601    Return {
602        id: NodeId,
603        span: Span,
604        value: Option<Box<Expr>>,
605    },
606
607    /// A `break` expression, optionally with a value.
608    Break {
609        id: NodeId,
610        span: Span,
611        value: Option<Box<Expr>>,
612    },
613
614    /// A `continue` expression.
615    Continue { id: NodeId, span: Span },
616
617    /// `unreachable` — a diverging expression.
618    Unreachable { id: NodeId, span: Span },
619
620    /// A string interpolation: `"Hello, ${name}!"`.
621    Interpolation {
622        id: NodeId,
623        span: Span,
624        parts: Vec<InterpolationPart>,
625    },
626
627    /// A placeholder `_` used in pipe expressions.
628    Placeholder { id: NodeId, span: Span },
629
630    /// A type-check expression: `expr is Type[Args]`.
631    ///
632    /// Stores the full [`TypeExpr`] rather than converting to an expression,
633    /// so generic arguments (e.g. `List[Int]`) are preserved.
634    Is {
635        id: NodeId,
636        span: Span,
637        expr: Box<Expr>,
638        type_expr: TypeExpr,
639    },
640}
641
642impl Expr {
643    /// Returns this node's [`NodeId`].
644    #[must_use]
645    pub fn node_id(&self) -> NodeId {
646        match self {
647            Expr::Literal { id, .. }
648            | Expr::Identifier { id, .. }
649            | Expr::Binary { id, .. }
650            | Expr::Unary { id, .. }
651            | Expr::Assign { id, .. }
652            | Expr::Call { id, .. }
653            | Expr::MethodCall { id, .. }
654            | Expr::FieldAccess { id, .. }
655            | Expr::Index { id, .. }
656            | Expr::Try { id, .. }
657            | Expr::Lambda { id, .. }
658            | Expr::Pipe { id, .. }
659            | Expr::Compose { id, .. }
660            | Expr::If { id, .. }
661            | Expr::Match { id, .. }
662            | Expr::Loop { id, .. }
663            | Expr::Block { id, .. }
664            | Expr::RecordConstruct { id, .. }
665            | Expr::ListLiteral { id, .. }
666            | Expr::MapLiteral { id, .. }
667            | Expr::SetLiteral { id, .. }
668            | Expr::TupleLiteral { id, .. }
669            | Expr::Range { id, .. }
670            | Expr::Await { id, .. }
671            | Expr::Return { id, .. }
672            | Expr::Break { id, .. }
673            | Expr::Continue { id, .. }
674            | Expr::Unreachable { id, .. }
675            | Expr::Interpolation { id, .. }
676            | Expr::Placeholder { id, .. }
677            | Expr::Is { id, .. } => *id,
678        }
679    }
680
681    /// Returns this node's source [`Span`].
682    #[must_use]
683    pub fn span(&self) -> Span {
684        match self {
685            Expr::Literal { span, .. }
686            | Expr::Identifier { span, .. }
687            | Expr::Binary { span, .. }
688            | Expr::Unary { span, .. }
689            | Expr::Assign { span, .. }
690            | Expr::Call { span, .. }
691            | Expr::MethodCall { span, .. }
692            | Expr::FieldAccess { span, .. }
693            | Expr::Index { span, .. }
694            | Expr::Try { span, .. }
695            | Expr::Lambda { span, .. }
696            | Expr::Pipe { span, .. }
697            | Expr::Compose { span, .. }
698            | Expr::If { span, .. }
699            | Expr::Match { span, .. }
700            | Expr::Loop { span, .. }
701            | Expr::Block { span, .. }
702            | Expr::RecordConstruct { span, .. }
703            | Expr::ListLiteral { span, .. }
704            | Expr::MapLiteral { span, .. }
705            | Expr::SetLiteral { span, .. }
706            | Expr::TupleLiteral { span, .. }
707            | Expr::Range { span, .. }
708            | Expr::Await { span, .. }
709            | Expr::Return { span, .. }
710            | Expr::Break { span, .. }
711            | Expr::Continue { span, .. }
712            | Expr::Unreachable { span, .. }
713            | Expr::Interpolation { span, .. }
714            | Expr::Placeholder { span, .. }
715            | Expr::Is { span, .. } => *span,
716        }
717    }
718}
719
720/// One segment of a string interpolation: either raw text or an embedded expression.
721#[derive(Debug, Clone, PartialEq)]
722pub enum InterpolationPart {
723    /// A raw string segment.
724    Literal(String),
725    /// An embedded expression: `${expr}`.
726    Expr(Expr),
727}
728
729// ─── Statements ───────────────────────────────────────────────────────────────
730
731/// A statement node.
732#[derive(Debug, Clone, PartialEq)]
733pub enum Stmt {
734    /// A `let` binding.
735    Let(LetStmt),
736    /// An expression used as a statement (usually has side effects).
737    Expr(Expr),
738    /// A `for` loop.
739    For(ForLoop),
740    /// A `while` loop.
741    While(WhileLoop),
742    /// An infinite `loop`.
743    Loop(LoopStmt),
744    /// A `guard` statement.
745    Guard(GuardStmt),
746    /// A `handling` block.
747    Handling(HandlingBlock),
748    /// An empty statement (bare semicolon or newline).
749    Empty,
750}
751
752/// A block of statements with an optional tail expression.
753#[derive(Debug, Clone, PartialEq)]
754pub struct Block {
755    pub id: NodeId,
756    pub span: Span,
757    pub stmts: Vec<Stmt>,
758    /// The final expression whose value becomes the block's value (no trailing newline).
759    pub tail: Option<Box<Expr>>,
760}
761
762/// `let [mut] pattern [: Type] = value`.
763#[derive(Debug, Clone, PartialEq)]
764pub struct LetStmt {
765    pub id: NodeId,
766    pub span: Span,
767    pub pattern: Pattern,
768    pub ty: Option<TypeExpr>,
769    pub value: Expr,
770}
771
772/// `for pattern in iterable { body }`.
773#[derive(Debug, Clone, PartialEq)]
774pub struct ForLoop {
775    pub id: NodeId,
776    pub span: Span,
777    pub pattern: Pattern,
778    pub iterable: Expr,
779    pub body: Block,
780}
781
782/// `while (condition) { body }`.
783#[derive(Debug, Clone, PartialEq)]
784pub struct WhileLoop {
785    pub id: NodeId,
786    pub span: Span,
787    pub condition: Expr,
788    pub body: Block,
789}
790
791/// `loop { body }`.
792#[derive(Debug, Clone, PartialEq)]
793pub struct LoopStmt {
794    pub id: NodeId,
795    pub span: Span,
796    pub body: Block,
797}
798
799/// `guard (condition) else { diverging_block }`.
800///
801/// Supports `guard (let pat = expr) else { ... }` when `let_pattern` is `Some`.
802#[derive(Debug, Clone, PartialEq)]
803pub struct GuardStmt {
804    pub id: NodeId,
805    pub span: Span,
806    /// For `guard (let pat = expr)`, holds the pattern.
807    pub let_pattern: Option<Pattern>,
808    pub condition: Expr,
809    pub else_block: Block,
810}
811
812/// `handling (Effect with handler, ...) { body }`.
813#[derive(Debug, Clone, PartialEq)]
814pub struct HandlingBlock {
815    pub id: NodeId,
816    pub span: Span,
817    pub handlers: Vec<HandlerPair>,
818    pub body: Block,
819}
820
821// ─── Function parameters ──────────────────────────────────────────────────────
822
823/// A single function parameter.
824#[derive(Debug, Clone, PartialEq)]
825pub struct Param {
826    pub id: NodeId,
827    pub span: Span,
828    pub pattern: Pattern,
829    pub ty: Option<TypeExpr>,
830    /// Optional default value.
831    pub default: Option<Expr>,
832}
833
834// ─── Declarations ────────────────────────────────────────────────────────────
835
836/// A function declaration.
837#[derive(Debug, Clone, PartialEq)]
838pub struct FnDecl {
839    pub id: NodeId,
840    pub span: Span,
841    pub annotations: Vec<Annotation>,
842    pub visibility: Visibility,
843    pub is_async: bool,
844    pub name: Ident,
845    pub generic_params: Vec<GenericParam>,
846    pub params: Vec<Param>,
847    pub return_type: Option<TypeExpr>,
848    /// Effects listed in the `with` clause.
849    pub effect_clause: Vec<TypePath>,
850    pub where_clause: Vec<TypeConstraint>,
851    /// `None` means a required trait method (no default body).
852    pub body: Option<Block>,
853}
854
855/// A record (value-type) declaration.
856#[derive(Debug, Clone, PartialEq)]
857pub struct RecordDecl {
858    pub id: NodeId,
859    pub span: Span,
860    pub annotations: Vec<Annotation>,
861    pub visibility: Visibility,
862    pub name: Ident,
863    pub generic_params: Vec<GenericParam>,
864    pub where_clause: Vec<TypeConstraint>,
865    pub fields: Vec<RecordDeclField>,
866}
867
868/// A field in a record declaration.
869#[derive(Debug, Clone, PartialEq)]
870pub struct RecordDeclField {
871    pub id: NodeId,
872    pub span: Span,
873    pub name: Ident,
874    pub ty: TypeExpr,
875    pub default: Option<Expr>,
876}
877
878/// An enum (algebraic data type) declaration.
879#[derive(Debug, Clone, PartialEq)]
880pub struct EnumDecl {
881    pub id: NodeId,
882    pub span: Span,
883    pub annotations: Vec<Annotation>,
884    pub visibility: Visibility,
885    pub name: Ident,
886    pub generic_params: Vec<GenericParam>,
887    pub where_clause: Vec<TypeConstraint>,
888    pub variants: Vec<EnumVariant>,
889}
890
891/// A single enum variant.
892#[derive(Debug, Clone, PartialEq)]
893pub enum EnumVariant {
894    /// A unit variant: `Variant1`.
895    Unit { id: NodeId, span: Span, name: Ident },
896    /// A struct-like variant: `Variant2 { field: Type }`.
897    Struct {
898        id: NodeId,
899        span: Span,
900        name: Ident,
901        fields: Vec<RecordDeclField>,
902    },
903    /// A tuple-like variant: `Variant3(Type, Type)`.
904    Tuple {
905        id: NodeId,
906        span: Span,
907        name: Ident,
908        tys: Vec<TypeExpr>,
909    },
910}
911
912/// A class declaration.
913#[derive(Debug, Clone, PartialEq)]
914pub struct ClassDecl {
915    pub id: NodeId,
916    pub span: Span,
917    pub annotations: Vec<Annotation>,
918    pub visibility: Visibility,
919    pub name: Ident,
920    pub generic_params: Vec<GenericParam>,
921    /// Single base class.
922    pub base: Option<TypePath>,
923    /// Implemented traits.
924    pub traits: Vec<TypePath>,
925    pub where_clause: Vec<TypeConstraint>,
926    pub fields: Vec<RecordDeclField>,
927    pub methods: Vec<FnDecl>,
928}
929
930/// A trait declaration (or platform-trait when `is_platform` is true).
931#[derive(Debug, Clone, PartialEq)]
932pub struct TraitDecl {
933    pub id: NodeId,
934    pub span: Span,
935    pub annotations: Vec<Annotation>,
936    pub visibility: Visibility,
937    pub is_platform: bool,
938    pub name: Ident,
939    pub generic_params: Vec<GenericParam>,
940    /// Supertraits: `trait Name: Super1, Super2`.
941    pub supertraits: Vec<TypePath>,
942    pub associated_types: Vec<AssociatedType>,
943    pub methods: Vec<FnDecl>,
944}
945
946/// An associated type inside a trait declaration.
947#[derive(Debug, Clone, PartialEq)]
948pub struct AssociatedType {
949    pub id: NodeId,
950    pub span: Span,
951    pub name: Ident,
952    pub bounds: Vec<TypePath>,
953}
954
955/// A type assignment inside an impl block: `type Output = Int`.
956#[derive(Debug, Clone, PartialEq)]
957pub struct TypeAssignment {
958    pub id: NodeId,
959    pub span: Span,
960    pub name: Ident,
961    pub type_expr: TypeExpr,
962}
963
964/// An `impl Trait for Type` or `impl Type` block.
965#[derive(Debug, Clone, PartialEq)]
966pub struct ImplBlock {
967    pub id: NodeId,
968    pub span: Span,
969    pub annotations: Vec<Annotation>,
970    pub generic_params: Vec<GenericParam>,
971    /// The trait being implemented, if any.
972    pub trait_path: Option<TypePath>,
973    /// The type being implemented.
974    pub target: TypeExpr,
975    pub where_clause: Vec<TypeConstraint>,
976    /// Associated type assignments: `type Output = Int`.
977    pub type_assignments: Vec<TypeAssignment>,
978    pub methods: Vec<FnDecl>,
979}
980
981/// An algebraic effect declaration.
982#[derive(Debug, Clone, PartialEq)]
983pub struct EffectDecl {
984    pub id: NodeId,
985    pub span: Span,
986    pub annotations: Vec<Annotation>,
987    pub visibility: Visibility,
988    pub name: Ident,
989    pub generic_params: Vec<GenericParam>,
990    /// Component effects for composite effects: `effect IO = Log + Clock`.
991    pub components: Vec<TypePath>,
992    pub operations: Vec<FnDecl>,
993}
994
995/// A `type Name = Type where (predicate)` alias.
996#[derive(Debug, Clone, PartialEq)]
997pub struct TypeAliasDecl {
998    pub id: NodeId,
999    pub span: Span,
1000    pub annotations: Vec<Annotation>,
1001    pub visibility: Visibility,
1002    pub name: Ident,
1003    pub generic_params: Vec<GenericParam>,
1004    pub ty: TypeExpr,
1005    pub where_clause: Vec<TypeConstraint>,
1006}
1007
1008/// A `const NAME: Type = value` declaration.
1009#[derive(Debug, Clone, PartialEq)]
1010pub struct ConstDecl {
1011    pub id: NodeId,
1012    pub span: Span,
1013    pub annotations: Vec<Annotation>,
1014    pub visibility: Visibility,
1015    pub name: Ident,
1016    pub ty: TypeExpr,
1017    pub value: Expr,
1018}
1019
1020/// A module-level `handle Effect with handler` declaration.
1021#[derive(Debug, Clone, PartialEq)]
1022pub struct ModuleHandleDecl {
1023    pub id: NodeId,
1024    pub span: Span,
1025    /// The effect being handled.
1026    pub effect: TypePath,
1027    /// The handler expression.
1028    pub handler: Expr,
1029}
1030
1031/// A `property("name") { forall(...) { ... } }` property-based test declaration.
1032#[derive(Debug, Clone, PartialEq)]
1033pub struct PropertyTestDecl {
1034    pub id: NodeId,
1035    pub span: Span,
1036    /// The test description string.
1037    pub name: String,
1038    /// `forall` bindings.
1039    pub bindings: Vec<PropertyBinding>,
1040    pub body: Block,
1041}
1042
1043// ─── Imports ─────────────────────────────────────────────────────────────────
1044
1045/// An import declaration.
1046#[derive(Debug, Clone, PartialEq)]
1047pub struct ImportDecl {
1048    pub id: NodeId,
1049    pub span: Span,
1050    pub visibility: Visibility,
1051    pub path: ModulePath,
1052    pub items: ImportItems,
1053}
1054
1055/// The items selected from an import.
1056#[derive(Debug, Clone, PartialEq)]
1057pub enum ImportItems {
1058    /// `import Foo` — import the module itself.
1059    Module,
1060    /// `import Foo.{ A, B }` — import specific names.
1061    Named(Vec<ImportedName>),
1062    /// `import Foo.*` — glob import.
1063    Glob,
1064}
1065
1066/// One named item in an import list.
1067#[derive(Debug, Clone, PartialEq)]
1068pub struct ImportedName {
1069    pub span: Span,
1070    pub name: Ident,
1071    /// `import Foo.{ Bar as Baz }` — rename.
1072    pub alias: Option<Ident>,
1073}
1074
1075// ─── Top-level items ──────────────────────────────────────────────────────────
1076
1077/// A top-level item in a source file.
1078#[derive(Debug, Clone, PartialEq)]
1079pub enum Item {
1080    Fn(FnDecl),
1081    Record(RecordDecl),
1082    Enum(EnumDecl),
1083    Class(ClassDecl),
1084    Trait(TraitDecl),
1085    PlatformTrait(TraitDecl),
1086    Impl(ImplBlock),
1087    Effect(EffectDecl),
1088    TypeAlias(TypeAliasDecl),
1089    Const(ConstDecl),
1090    /// Module-level `handle Effect with handler`.
1091    ModuleHandle(ModuleHandleDecl),
1092    /// `property("name") { forall(...) { ... } }`.
1093    PropertyTest(PropertyTestDecl),
1094    /// Error recovery node: wraps tokens that could not be parsed.
1095    Error {
1096        id: NodeId,
1097        span: Span,
1098    },
1099}
1100
1101impl Item {
1102    /// Returns the primary [`Span`] of this item.
1103    #[must_use]
1104    pub fn span(&self) -> Span {
1105        match self {
1106            Item::Fn(d) => d.span,
1107            Item::Record(d) => d.span,
1108            Item::Enum(d) => d.span,
1109            Item::Class(d) => d.span,
1110            Item::Trait(d) | Item::PlatformTrait(d) => d.span,
1111            Item::Impl(d) => d.span,
1112            Item::Effect(d) => d.span,
1113            Item::TypeAlias(d) => d.span,
1114            Item::Const(d) => d.span,
1115            Item::ModuleHandle(d) => d.span,
1116            Item::PropertyTest(d) => d.span,
1117            Item::Error { span, .. } => *span,
1118        }
1119    }
1120}
1121
1122// ─── Module (root) ────────────────────────────────────────────────────────────
1123
1124/// The root AST node for a single Bock source file.
1125#[derive(Debug, Clone, PartialEq)]
1126pub struct Module {
1127    pub id: NodeId,
1128    pub span: Span,
1129    /// Module-level doc comments.
1130    pub doc: Vec<String>,
1131    /// Optional `module Foo.Bar` declaration.
1132    pub path: Option<ModulePath>,
1133    pub imports: Vec<ImportDecl>,
1134    pub items: Vec<Item>,
1135}
1136
1137// ─── Tests ───────────────────────────────────────────────────────────────────
1138
1139#[cfg(test)]
1140mod tests {
1141    use super::*;
1142    use bock_errors::FileId;
1143
1144    fn dummy_span() -> Span {
1145        Span {
1146            file: FileId(0),
1147            start: 0,
1148            end: 0,
1149        }
1150    }
1151
1152    fn dummy_ident(name: &str) -> Ident {
1153        Ident {
1154            name: name.to_string(),
1155            span: dummy_span(),
1156        }
1157    }
1158
1159    #[test]
1160    fn module_is_debug() {
1161        let m = Module {
1162            id: 0,
1163            span: dummy_span(),
1164            doc: vec![],
1165            path: None,
1166            imports: vec![],
1167            items: vec![],
1168        };
1169        let s = format!("{m:?}");
1170        assert!(s.contains("Module"));
1171    }
1172
1173    #[test]
1174    fn item_fn_span() {
1175        let fn_decl = FnDecl {
1176            id: 1,
1177            span: Span {
1178                file: FileId(1),
1179                start: 5,
1180                end: 20,
1181            },
1182            annotations: vec![],
1183            visibility: Visibility::Public,
1184            is_async: false,
1185            name: dummy_ident("foo"),
1186            generic_params: vec![],
1187            params: vec![],
1188            return_type: None,
1189            effect_clause: vec![],
1190            where_clause: vec![],
1191            body: Some(Block {
1192                id: 2,
1193                span: Span {
1194                    file: FileId(1),
1195                    start: 10,
1196                    end: 20,
1197                },
1198                stmts: vec![],
1199                tail: None,
1200            }),
1201        };
1202        let item = Item::Fn(fn_decl);
1203        assert_eq!(item.span().start, 5);
1204    }
1205
1206    #[test]
1207    fn item_module_handle_and_property_test_exist() {
1208        let mh = Item::ModuleHandle(ModuleHandleDecl {
1209            id: 10,
1210            span: dummy_span(),
1211            effect: TypePath {
1212                segments: vec![dummy_ident("Log")],
1213                span: dummy_span(),
1214            },
1215            handler: Expr::Identifier {
1216                id: 11,
1217                span: dummy_span(),
1218                name: dummy_ident("console_log"),
1219            },
1220        });
1221        let pt = Item::PropertyTest(PropertyTestDecl {
1222            id: 20,
1223            span: dummy_span(),
1224            name: "addition is commutative".into(),
1225            bindings: vec![],
1226            body: Block {
1227                id: 21,
1228                span: dummy_span(),
1229                stmts: vec![],
1230                tail: None,
1231            },
1232        });
1233        assert!(format!("{mh:?}").contains("ModuleHandle"));
1234        assert!(format!("{pt:?}").contains("PropertyTest"));
1235    }
1236
1237    #[test]
1238    fn expr_node_id_and_span() {
1239        let span = Span {
1240            file: FileId(1),
1241            start: 3,
1242            end: 7,
1243        };
1244        let e = Expr::Literal {
1245            id: 42,
1246            span,
1247            lit: Literal::Int("42".into()),
1248        };
1249        assert_eq!(e.node_id(), 42);
1250        assert_eq!(e.span(), span);
1251    }
1252
1253    #[test]
1254    fn pattern_wildcard_debug() {
1255        let p = Pattern::Wildcard {
1256            id: 0,
1257            span: dummy_span(),
1258        };
1259        assert!(format!("{p:?}").contains("Wildcard"));
1260    }
1261
1262    #[test]
1263    fn type_expr_optional_debug() {
1264        let inner = TypeExpr::Named {
1265            id: 0,
1266            span: dummy_span(),
1267            path: TypePath {
1268                segments: vec![dummy_ident("Int")],
1269                span: dummy_span(),
1270            },
1271            args: vec![],
1272        };
1273        let opt = TypeExpr::Optional {
1274            id: 1,
1275            span: dummy_span(),
1276            inner: Box::new(inner),
1277        };
1278        assert!(format!("{opt:?}").contains("Optional"));
1279    }
1280
1281    #[test]
1282    fn visibility_default_is_private() {
1283        assert_eq!(Visibility::default(), Visibility::Private);
1284    }
1285
1286    #[test]
1287    fn enum_variant_kinds() {
1288        let unit = EnumVariant::Unit {
1289            id: 0,
1290            span: dummy_span(),
1291            name: dummy_ident("A"),
1292        };
1293        let strukt = EnumVariant::Struct {
1294            id: 1,
1295            span: dummy_span(),
1296            name: dummy_ident("B"),
1297            fields: vec![],
1298        };
1299        let tuple = EnumVariant::Tuple {
1300            id: 2,
1301            span: dummy_span(),
1302            name: dummy_ident("C"),
1303            tys: vec![],
1304        };
1305        assert!(format!("{unit:?}").contains("Unit"));
1306        assert!(format!("{strukt:?}").contains("Struct"));
1307        assert!(format!("{tuple:?}").contains("Tuple"));
1308    }
1309
1310    #[test]
1311    fn all_expr_variants_have_span() {
1312        let span = dummy_span();
1313        let exprs: Vec<Expr> = vec![
1314            Expr::Literal {
1315                id: 0,
1316                span,
1317                lit: Literal::Bool(true),
1318            },
1319            Expr::Identifier {
1320                id: 1,
1321                span,
1322                name: dummy_ident("x"),
1323            },
1324            Expr::Continue { id: 2, span },
1325            Expr::Unreachable { id: 3, span },
1326            Expr::Placeholder { id: 4, span },
1327        ];
1328        for e in &exprs {
1329            assert_eq!(e.span(), span);
1330        }
1331    }
1332}