Skip to main content

bock_air/
node.rs

1//! AIR node definitions — the unified intermediate representation.
2//!
3//! Every construct in an Bock program is represented as an [`AIRNode`] with a
4//! [`NodeKind`] discriminant that carries typed children. All four AIR layers
5//! (S-AIR, T-AIR, C-AIR, TR-AIR) use the same node type; later passes fill
6//! in the layer slots that start as `None`.
7
8use std::collections::{HashMap, HashSet};
9use std::sync::atomic::{AtomicU32, Ordering};
10
11use bock_ast::{
12    Annotation, AssignOp, BinOp, GenericParam, Ident, ImportItems, Literal, ModulePath,
13    PropertyBinding, RecordDeclField, TypeConstraint, TypePath, UnaryOp, Visibility,
14};
15use bock_errors::Span;
16
17use crate::stubs::{
18    Capability, ContextBlock, EffectRef, OwnershipInfo, TargetInfo, TypeInfo, Value,
19};
20
21// ─── NodeId ───────────────────────────────────────────────────────────────────
22
23/// Unique identifier for an AIR node within a compilation session.
24pub type NodeId = u32;
25
26/// A monotonic counter that generates unique [`NodeId`]s.
27///
28/// Typically one `NodeIdGen` is created per compilation session and shared
29/// (via `&NodeIdGen`) across all lowering passes.
30#[derive(Debug, Default)]
31pub struct NodeIdGen {
32    counter: AtomicU32,
33}
34
35impl NodeIdGen {
36    /// Creates a new generator starting at zero.
37    #[must_use]
38    pub fn new() -> Self {
39        Self {
40            counter: AtomicU32::new(0),
41        }
42    }
43
44    /// Returns the next unique [`NodeId`].
45    #[must_use]
46    pub fn next(&self) -> NodeId {
47        self.counter.fetch_add(1, Ordering::SeqCst)
48    }
49}
50
51// ─── AIR node ─────────────────────────────────────────────────────────────────
52
53/// A single node in the Bock Intermediate Representation.
54///
55/// Each `AIRNode` carries:
56/// - A unique [`NodeId`] and source [`Span`]
57/// - A [`NodeKind`] with typed, structured children
58/// - Optional slots for each AIR layer (initially `None`, filled by passes)
59/// - An extensible metadata map for pass-specific annotations
60#[derive(Debug, Clone, PartialEq)]
61pub struct AIRNode {
62    /// Unique identifier for this node in the session.
63    pub id: NodeId,
64    /// Source location of this node.
65    pub span: Span,
66    /// Discriminant and typed children of this node.
67    pub kind: NodeKind,
68    // ── Layer 1 slots (populated by the type checker) ──────────────────────
69    /// Resolved type of this node (set by T-AIR pass).
70    pub type_info: Option<TypeInfo>,
71    /// Ownership/borrow annotation (set by T-AIR pass).
72    pub ownership: Option<OwnershipInfo>,
73    /// Algebraic effects this node may perform (set by T-AIR pass).
74    pub effects: HashSet<EffectRef>,
75    /// Capabilities this node requires (set by T-AIR pass).
76    pub capabilities: HashSet<Capability>,
77    // ── Layer 2 slot (populated by the context resolver) ──────────────────
78    /// Context annotations (set by C-AIR pass).
79    pub context: Option<ContextBlock>,
80    // ── Layer 3 slot (populated by the target analyzer) ───────────────────
81    /// Target-specific information (set by TR-AIR pass).
82    pub target: Option<TargetInfo>,
83    // ── Extensible metadata ────────────────────────────────────────────────
84    /// Arbitrary pass-specific metadata keyed by string.
85    pub metadata: HashMap<String, Value>,
86}
87
88impl AIRNode {
89    /// Creates a new S-AIR node with all layer slots empty.
90    #[must_use]
91    pub fn new(id: NodeId, span: Span, kind: NodeKind) -> Self {
92        Self {
93            id,
94            span,
95            kind,
96            type_info: None,
97            ownership: None,
98            effects: HashSet::new(),
99            capabilities: HashSet::new(),
100            context: None,
101            target: None,
102            metadata: HashMap::new(),
103        }
104    }
105}
106
107// ─── Auxiliary types ──────────────────────────────────────────────────────────
108
109/// A named argument in a call expression: `label: value`.
110#[derive(Debug, Clone, PartialEq)]
111pub struct AirArg {
112    /// Optional call-site label (e.g. `with:`, `from:`).
113    pub label: Option<Ident>,
114    /// The argument expression.
115    pub value: AIRNode,
116}
117
118/// A field in a record construction expression.
119#[derive(Debug, Clone, PartialEq)]
120pub struct AirRecordField {
121    /// Field name.
122    pub name: Ident,
123    /// `None` means shorthand: `{ name }` ≡ `{ name: name }`.
124    pub value: Option<Box<AIRNode>>,
125}
126
127/// A field binding inside a record pattern.
128#[derive(Debug, Clone, PartialEq)]
129pub struct AirRecordPatternField {
130    /// Field name.
131    pub name: Ident,
132    /// `None` means shorthand: `{ name }` ≡ `{ name: name }`.
133    pub pattern: Option<Box<AIRNode>>,
134}
135
136/// A key-value entry in a map literal.
137#[derive(Debug, Clone, PartialEq)]
138pub struct AirMapEntry {
139    pub key: AIRNode,
140    pub value: AIRNode,
141}
142
143/// A handler pair in a `handling` block: `Effect with handler`.
144#[derive(Debug, Clone, PartialEq)]
145pub struct AirHandlerPair {
146    /// The effect being handled.
147    pub effect: TypePath,
148    /// The handler expression node.
149    pub handler: Box<AIRNode>,
150}
151
152/// A segment of a string interpolation expression.
153#[derive(Debug, Clone, PartialEq)]
154pub enum AirInterpolationPart {
155    /// A literal string segment.
156    Literal(String),
157    /// An embedded expression `${expr}`.
158    Expr(Box<AIRNode>),
159}
160
161/// `Ok` or `Err` variant for result construction.
162#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum ResultVariant {
164    Ok,
165    Err,
166}
167
168// ─── NodeKind ─────────────────────────────────────────────────────────────────
169
170/// Discriminant and typed children of an [`AIRNode`].
171///
172/// Children are structured per variant (not a flat `Vec<AIRNode>`), mirroring
173/// the AST but lowered into the unified AIR representation.
174#[derive(Debug, Clone, PartialEq)]
175#[non_exhaustive]
176pub enum NodeKind {
177    // ── Module ────────────────────────────────────────────────────────────
178    /// The root node of a compiled Bock source file.
179    Module {
180        path: Option<ModulePath>,
181        /// Module-level annotations (`@context`, `@requires`, etc.).
182        annotations: Vec<Annotation>,
183        /// Import declarations (`NodeKind::ImportDecl`).
184        imports: Vec<AIRNode>,
185        /// Top-level items.
186        items: Vec<AIRNode>,
187    },
188
189    /// An import declaration: `import Foo.Bar.{ A, B }`.
190    ImportDecl {
191        path: ModulePath,
192        items: ImportItems,
193    },
194
195    // ── Declarations ──────────────────────────────────────────────────────
196    /// A function declaration.
197    FnDecl {
198        annotations: Vec<Annotation>,
199        visibility: Visibility,
200        is_async: bool,
201        name: Ident,
202        generic_params: Vec<GenericParam>,
203        /// Parameter nodes (`NodeKind::Param`).
204        params: Vec<AIRNode>,
205        /// Optional return-type node (a type-expression variant).
206        return_type: Option<Box<AIRNode>>,
207        /// Effect names listed in the `with` clause.
208        effect_clause: Vec<TypePath>,
209        where_clause: Vec<TypeConstraint>,
210        /// Body block node (`NodeKind::Block`).
211        body: Box<AIRNode>,
212    },
213
214    /// A record (value-type) declaration.
215    RecordDecl {
216        annotations: Vec<Annotation>,
217        visibility: Visibility,
218        name: Ident,
219        generic_params: Vec<GenericParam>,
220        fields: Vec<RecordDeclField>,
221    },
222
223    /// An enum (algebraic data type) declaration.
224    EnumDecl {
225        annotations: Vec<Annotation>,
226        visibility: Visibility,
227        name: Ident,
228        generic_params: Vec<GenericParam>,
229        /// Variant nodes (unit, struct, or tuple — see AST `EnumVariant`).
230        variants: Vec<AIRNode>,
231    },
232
233    /// An enum variant — unit, struct-like, or tuple-like.
234    EnumVariant {
235        name: Ident,
236        /// `None` = unit; `Some` = struct fields or positional types.
237        payload: EnumVariantPayload,
238    },
239
240    /// A class declaration.
241    ClassDecl {
242        annotations: Vec<Annotation>,
243        visibility: Visibility,
244        name: Ident,
245        generic_params: Vec<GenericParam>,
246        base: Option<TypePath>,
247        traits: Vec<TypePath>,
248        fields: Vec<RecordDeclField>,
249        /// Method nodes (`NodeKind::FnDecl`).
250        methods: Vec<AIRNode>,
251    },
252
253    /// A trait (or platform-trait) declaration.
254    TraitDecl {
255        annotations: Vec<Annotation>,
256        visibility: Visibility,
257        is_platform: bool,
258        name: Ident,
259        generic_params: Vec<GenericParam>,
260        associated_types: Vec<bock_ast::AssociatedType>,
261        /// Method nodes (`NodeKind::FnDecl`).
262        methods: Vec<AIRNode>,
263    },
264
265    /// An `impl Trait for Type` or `impl Type` block.
266    ImplBlock {
267        annotations: Vec<Annotation>,
268        generic_params: Vec<GenericParam>,
269        trait_path: Option<TypePath>,
270        /// The type being implemented (a type-expression node).
271        target: Box<AIRNode>,
272        where_clause: Vec<TypeConstraint>,
273        /// Method nodes (`NodeKind::FnDecl`).
274        methods: Vec<AIRNode>,
275    },
276
277    /// An algebraic effect declaration.
278    EffectDecl {
279        annotations: Vec<Annotation>,
280        visibility: Visibility,
281        name: Ident,
282        generic_params: Vec<GenericParam>,
283        /// Component effects for composite effects: `effect IO = Log + Clock`.
284        components: Vec<TypePath>,
285        /// Operation nodes (`NodeKind::FnDecl`).
286        operations: Vec<AIRNode>,
287    },
288
289    /// A type alias: `type Name[T] = ...`.
290    TypeAlias {
291        annotations: Vec<Annotation>,
292        visibility: Visibility,
293        name: Ident,
294        generic_params: Vec<GenericParam>,
295        /// The aliased type-expression node.
296        ty: Box<AIRNode>,
297        where_clause: Vec<TypeConstraint>,
298    },
299
300    /// A constant declaration: `const NAME: Type = value`.
301    ConstDecl {
302        annotations: Vec<Annotation>,
303        visibility: Visibility,
304        name: Ident,
305        /// Type annotation node (type-expression variant).
306        ty: Box<AIRNode>,
307        /// Initialiser expression node.
308        value: Box<AIRNode>,
309    },
310
311    /// A module-level `handle Effect with handler` declaration.
312    ModuleHandle {
313        effect: TypePath,
314        /// Handler expression node.
315        handler: Box<AIRNode>,
316    },
317
318    /// A `property("name") { forall(...) { ... } }` property-based test.
319    PropertyTest {
320        name: String,
321        bindings: Vec<PropertyBinding>,
322        /// Body block node (`NodeKind::Block`).
323        body: Box<AIRNode>,
324    },
325
326    // ── Function parameter ────────────────────────────────────────────────
327    /// A single function/lambda parameter.
328    Param {
329        /// Pattern node (a pattern variant).
330        pattern: Box<AIRNode>,
331        /// Optional type annotation node (type-expression variant).
332        ty: Option<Box<AIRNode>>,
333        /// Optional default-value expression node.
334        default: Option<Box<AIRNode>>,
335    },
336
337    // ── Type expressions ──────────────────────────────────────────────────
338    /// A named type, possibly with generic arguments: `List[Int]`.
339    TypeNamed {
340        path: TypePath,
341        /// Generic argument nodes (type-expression variants).
342        args: Vec<AIRNode>,
343    },
344
345    /// A tuple type: `(Int, String)`.
346    TypeTuple {
347        /// Element type nodes (type-expression variants).
348        elems: Vec<AIRNode>,
349    },
350
351    /// A function type: `Fn(Int) -> String with Log`.
352    TypeFunction {
353        /// Parameter type nodes (type-expression variants).
354        params: Vec<AIRNode>,
355        /// Return type node (type-expression variant).
356        ret: Box<AIRNode>,
357        /// Effects listed in the `with` clause.
358        effects: Vec<TypePath>,
359    },
360
361    /// An optional type: `Int?`.
362    TypeOptional {
363        /// Inner type node (type-expression variant).
364        inner: Box<AIRNode>,
365    },
366
367    /// The `Self` type in a trait/impl context.
368    TypeSelf,
369
370    // ── Expressions ───────────────────────────────────────────────────────
371    /// A literal value.
372    Literal { lit: Literal },
373
374    /// An identifier reference.
375    Identifier { name: Ident },
376
377    /// A binary operation: `a + b`.
378    BinaryOp {
379        op: BinOp,
380        left: Box<AIRNode>,
381        right: Box<AIRNode>,
382    },
383
384    /// A unary operation: `-x`, `!flag`.
385    UnaryOp { op: UnaryOp, operand: Box<AIRNode> },
386
387    /// An assignment expression: `x = 5`, `x += 1`.
388    Assign {
389        op: AssignOp,
390        target: Box<AIRNode>,
391        value: Box<AIRNode>,
392    },
393
394    /// A function call: `f(a, b)`.
395    Call {
396        callee: Box<AIRNode>,
397        args: Vec<AirArg>,
398        type_args: Vec<AIRNode>,
399    },
400
401    /// A method call: `obj.method(a, b)`.
402    MethodCall {
403        receiver: Box<AIRNode>,
404        method: Ident,
405        type_args: Vec<AIRNode>,
406        args: Vec<AirArg>,
407    },
408
409    /// Field access: `obj.field`.
410    FieldAccess { object: Box<AIRNode>, field: Ident },
411
412    /// Index access: `arr[i]`.
413    Index {
414        object: Box<AIRNode>,
415        index: Box<AIRNode>,
416    },
417
418    /// Error propagation: `expr?` — maps to spec's `Propagate`.
419    Propagate { expr: Box<AIRNode> },
420
421    /// A lambda: `(x) => x * 2`.
422    Lambda {
423        /// Parameter nodes (`NodeKind::Param`).
424        params: Vec<AIRNode>,
425        /// Body expression node.
426        body: Box<AIRNode>,
427    },
428
429    /// Pipe operator: `data |> parse`.
430    Pipe {
431        left: Box<AIRNode>,
432        right: Box<AIRNode>,
433    },
434
435    /// Function composition: `parse >> validate`.
436    Compose {
437        left: Box<AIRNode>,
438        right: Box<AIRNode>,
439    },
440
441    /// An `await` expression.
442    Await { expr: Box<AIRNode> },
443
444    /// A range: `1..10` (exclusive) or `1..=10` (inclusive).
445    Range {
446        lo: Box<AIRNode>,
447        hi: Box<AIRNode>,
448        inclusive: bool,
449    },
450
451    /// Record construction: `User { id: 1, name, ..defaults }`.
452    RecordConstruct {
453        path: TypePath,
454        fields: Vec<AirRecordField>,
455        spread: Option<Box<AIRNode>>,
456    },
457
458    /// List literal: `[1, 2, 3]`.
459    ListLiteral { elems: Vec<AIRNode> },
460
461    /// Map literal: `{"key": value}`.
462    MapLiteral { entries: Vec<AirMapEntry> },
463
464    /// Set literal: `#{"a", "b"}`.
465    SetLiteral { elems: Vec<AIRNode> },
466
467    /// Tuple literal: `("hello", 42)`.
468    TupleLiteral { elems: Vec<AIRNode> },
469
470    /// String interpolation: `"Hello, ${name}!"`.
471    Interpolation { parts: Vec<AirInterpolationPart> },
472
473    /// A placeholder `_` used in pipe expressions.
474    Placeholder,
475
476    /// `unreachable` — a diverging expression.
477    Unreachable,
478
479    /// Explicit `Ok(v)` or `Err(e)` result construction.
480    ResultConstruct {
481        variant: ResultVariant,
482        value: Option<Box<AIRNode>>,
483    },
484
485    // ── Control flow ──────────────────────────────────────────────────────
486    /// An `if` / `if-let` expression.
487    If {
488        /// For `if let pat = expr`, holds the pattern node.
489        let_pattern: Option<Box<AIRNode>>,
490        condition: Box<AIRNode>,
491        /// Then-branch block node (`NodeKind::Block`).
492        then_block: Box<AIRNode>,
493        /// Optional else branch (block or nested if node).
494        else_block: Option<Box<AIRNode>>,
495    },
496
497    /// A `guard condition else { ... }` statement.
498    ///
499    /// When `let_pattern` is `Some`, this is `guard (let pat = expr) else { ... }`.
500    Guard {
501        /// For `guard (let pat = expr)`, the pattern node.
502        let_pattern: Option<Box<AIRNode>>,
503        condition: Box<AIRNode>,
504        else_block: Box<AIRNode>,
505    },
506
507    /// A `match` expression.
508    Match {
509        scrutinee: Box<AIRNode>,
510        /// Match arm nodes (`NodeKind::MatchArm`).
511        arms: Vec<AIRNode>,
512    },
513
514    /// One arm of a `match` expression.
515    MatchArm {
516        /// Pattern node (a pattern variant).
517        pattern: Box<AIRNode>,
518        /// Optional guard expression.
519        guard: Option<Box<AIRNode>>,
520        /// Body expression node.
521        body: Box<AIRNode>,
522    },
523
524    /// A `for` loop.
525    For {
526        /// Loop variable pattern node (a pattern variant).
527        pattern: Box<AIRNode>,
528        iterable: Box<AIRNode>,
529        body: Box<AIRNode>,
530    },
531
532    /// A `while` loop.
533    While {
534        condition: Box<AIRNode>,
535        body: Box<AIRNode>,
536    },
537
538    /// An infinite `loop`.
539    Loop { body: Box<AIRNode> },
540
541    /// A block of statements with an optional tail expression.
542    Block {
543        stmts: Vec<AIRNode>,
544        tail: Option<Box<AIRNode>>,
545    },
546
547    /// A `return` expression.
548    Return { value: Option<Box<AIRNode>> },
549
550    /// A `break` expression, optionally with a value.
551    Break { value: Option<Box<AIRNode>> },
552
553    /// A `continue` expression.
554    Continue,
555
556    // ── Ownership ─────────────────────────────────────────────────────────
557    /// A `let [mut] pattern [: Type] = value` binding.
558    LetBinding {
559        is_mut: bool,
560        pattern: Box<AIRNode>,
561        ty: Option<Box<AIRNode>>,
562        value: Box<AIRNode>,
563    },
564
565    /// An explicit move of ownership: `move expr`.
566    Move { expr: Box<AIRNode> },
567
568    /// An immutable borrow: `&expr`.
569    Borrow { expr: Box<AIRNode> },
570
571    /// A mutable borrow: `&mut expr`.
572    MutableBorrow { expr: Box<AIRNode> },
573
574    // ── Effects ───────────────────────────────────────────────────────────
575    /// An algebraic-effect operation invocation.
576    EffectOp {
577        effect: TypePath,
578        operation: Ident,
579        args: Vec<AirArg>,
580    },
581
582    /// A `handling (Effect with handler, ...) { body }` block.
583    HandlingBlock {
584        handlers: Vec<AirHandlerPair>,
585        body: Box<AIRNode>,
586    },
587
588    /// A reference to an effect type (used in type positions and signatures).
589    EffectRef { path: TypePath },
590
591    // ── Patterns ──────────────────────────────────────────────────────────
592    /// `_` — wildcard pattern, matches anything.
593    WildcardPat,
594
595    /// `name` or `mut name` — bind pattern.
596    BindPat { name: Ident, is_mut: bool },
597
598    /// A literal pattern: `42`, `"hello"`, `true`.
599    LiteralPat { lit: Literal },
600
601    /// An enum constructor pattern: `Some(x)`, `Ok(v)`.
602    ConstructorPat {
603        path: TypePath,
604        /// Positional field pattern nodes.
605        fields: Vec<AIRNode>,
606    },
607
608    /// A record pattern: `User { name, age }`.
609    RecordPat {
610        path: TypePath,
611        fields: Vec<AirRecordPatternField>,
612        /// `true` when the pattern contains a `..` rest marker.
613        rest: bool,
614    },
615
616    /// A tuple pattern: `(a, b, c)`.
617    TuplePat { elems: Vec<AIRNode> },
618
619    /// A list pattern: `[head, ..tail]`.
620    ListPat {
621        elems: Vec<AIRNode>,
622        rest: Option<Box<AIRNode>>,
623    },
624
625    /// An or-pattern: `A | B`.
626    OrPat { alternatives: Vec<AIRNode> },
627
628    /// A guard pattern (in pattern-matching guard position).
629    GuardPat {
630        pattern: Box<AIRNode>,
631        guard: Box<AIRNode>,
632    },
633
634    /// A range pattern: `1..10` or `1..=10`.
635    RangePat {
636        lo: Box<AIRNode>,
637        hi: Box<AIRNode>,
638        inclusive: bool,
639    },
640
641    /// A rest pattern `..` (inside list/tuple patterns).
642    RestPat,
643
644    // ── Error recovery ────────────────────────────────────────────────────
645    /// An error-recovery node wrapping tokens that could not be lowered.
646    Error,
647}
648
649/// The payload of an enum variant in the AIR.
650#[derive(Debug, Clone, PartialEq)]
651pub enum EnumVariantPayload {
652    /// Unit variant: `Variant`.
653    Unit,
654    /// Struct-like variant: `Variant { field: Type, ... }`.
655    Struct(Vec<RecordDeclField>),
656    /// Tuple-like variant: `Variant(Type, Type)`.
657    Tuple(Vec<AIRNode>),
658}
659
660// ─── Tests ───────────────────────────────────────────────────────────────────
661
662#[cfg(test)]
663mod tests {
664    use super::*;
665    use bock_errors::FileId;
666
667    fn dummy_span() -> Span {
668        Span {
669            file: FileId(0),
670            start: 0,
671            end: 0,
672        }
673    }
674
675    fn dummy_ident(name: &str) -> Ident {
676        Ident {
677            name: name.to_string(),
678            span: dummy_span(),
679        }
680    }
681
682    fn make_node(id: NodeId, kind: NodeKind) -> AIRNode {
683        AIRNode::new(id, dummy_span(), kind)
684    }
685
686    // ── NodeIdGen ──────────────────────────────────────────────────────────
687
688    #[test]
689    fn node_id_gen_monotonic() {
690        let gen = NodeIdGen::new();
691        let a = gen.next();
692        let b = gen.next();
693        let c = gen.next();
694        assert_eq!(a, 0);
695        assert_eq!(b, 1);
696        assert_eq!(c, 2);
697    }
698
699    #[test]
700    fn node_id_gen_thread_safe() {
701        use std::sync::Arc;
702        use std::thread;
703
704        let gen = Arc::new(NodeIdGen::new());
705        let handles: Vec<_> = (0..4)
706            .map(|_| {
707                let g = Arc::clone(&gen);
708                thread::spawn(move || g.next())
709            })
710            .collect();
711        let mut ids: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
712        ids.sort();
713        // All IDs should be distinct (0..4 in some order)
714        assert_eq!(ids, vec![0, 1, 2, 3]);
715    }
716
717    // ── AIRNode basics ─────────────────────────────────────────────────────
718
719    #[test]
720    fn air_node_new_has_empty_slots() {
721        let node = make_node(42, NodeKind::Continue);
722        assert_eq!(node.id, 42);
723        assert!(node.type_info.is_none());
724        assert!(node.ownership.is_none());
725        assert!(node.effects.is_empty());
726        assert!(node.capabilities.is_empty());
727        assert!(node.context.is_none());
728        assert!(node.target.is_none());
729        assert!(node.metadata.is_empty());
730    }
731
732    #[test]
733    fn air_node_debug_contains_kind() {
734        let node = make_node(0, NodeKind::Unreachable);
735        let s = format!("{node:?}");
736        assert!(s.contains("Unreachable"));
737    }
738
739    // ── NodeKind coverage ─────────────────────────────────────────────────
740
741    #[test]
742    fn module_node() {
743        let n = make_node(
744            0,
745            NodeKind::Module {
746                path: None,
747                annotations: vec![],
748                imports: vec![],
749                items: vec![],
750            },
751        );
752        assert!(matches!(n.kind, NodeKind::Module { .. }));
753    }
754
755    #[test]
756    fn fn_decl_node() {
757        let body = make_node(
758            1,
759            NodeKind::Block {
760                stmts: vec![],
761                tail: None,
762            },
763        );
764        let n = make_node(
765            0,
766            NodeKind::FnDecl {
767                annotations: vec![],
768                visibility: Visibility::Public,
769                is_async: false,
770                name: dummy_ident("foo"),
771                generic_params: vec![],
772                params: vec![],
773                return_type: None,
774                effect_clause: vec![],
775                where_clause: vec![],
776                body: Box::new(body),
777            },
778        );
779        assert!(matches!(n.kind, NodeKind::FnDecl { .. }));
780    }
781
782    #[test]
783    fn binary_op_node() {
784        let left = make_node(
785            1,
786            NodeKind::Literal {
787                lit: Literal::Int("1".into()),
788            },
789        );
790        let right = make_node(
791            2,
792            NodeKind::Literal {
793                lit: Literal::Int("2".into()),
794            },
795        );
796        let n = make_node(
797            0,
798            NodeKind::BinaryOp {
799                op: BinOp::Add,
800                left: Box::new(left),
801                right: Box::new(right),
802            },
803        );
804        assert!(matches!(n.kind, NodeKind::BinaryOp { op: BinOp::Add, .. }));
805    }
806
807    #[test]
808    fn pattern_nodes() {
809        let wildcard = make_node(0, NodeKind::WildcardPat);
810        let bind = make_node(
811            1,
812            NodeKind::BindPat {
813                name: dummy_ident("x"),
814                is_mut: false,
815            },
816        );
817        let lit = make_node(
818            2,
819            NodeKind::LiteralPat {
820                lit: Literal::Bool(true),
821            },
822        );
823        assert!(matches!(wildcard.kind, NodeKind::WildcardPat));
824        assert!(matches!(bind.kind, NodeKind::BindPat { .. }));
825        assert!(matches!(lit.kind, NodeKind::LiteralPat { .. }));
826    }
827
828    #[test]
829    fn control_flow_nodes() {
830        let body = Box::new(make_node(
831            1,
832            NodeKind::Block {
833                stmts: vec![],
834                tail: None,
835            },
836        ));
837        let cond = Box::new(make_node(
838            2,
839            NodeKind::Literal {
840                lit: Literal::Bool(true),
841            },
842        ));
843
844        let while_node = make_node(
845            0,
846            NodeKind::While {
847                condition: cond.clone(),
848                body: body.clone(),
849            },
850        );
851        let loop_node = make_node(3, NodeKind::Loop { body: body.clone() });
852        let return_node = make_node(4, NodeKind::Return { value: None });
853        let break_node = make_node(5, NodeKind::Break { value: None });
854        let continue_node = make_node(6, NodeKind::Continue);
855
856        assert!(matches!(while_node.kind, NodeKind::While { .. }));
857        assert!(matches!(loop_node.kind, NodeKind::Loop { .. }));
858        assert!(matches!(return_node.kind, NodeKind::Return { value: None }));
859        assert!(matches!(break_node.kind, NodeKind::Break { value: None }));
860        assert!(matches!(continue_node.kind, NodeKind::Continue));
861    }
862
863    #[test]
864    fn ownership_nodes() {
865        let expr = Box::new(make_node(
866            1,
867            NodeKind::Identifier {
868                name: dummy_ident("x"),
869            },
870        ));
871        let mv = make_node(0, NodeKind::Move { expr: expr.clone() });
872        let borrow = make_node(2, NodeKind::Borrow { expr: expr.clone() });
873        let mut_borrow = make_node(3, NodeKind::MutableBorrow { expr: expr.clone() });
874        assert!(matches!(mv.kind, NodeKind::Move { .. }));
875        assert!(matches!(borrow.kind, NodeKind::Borrow { .. }));
876        assert!(matches!(mut_borrow.kind, NodeKind::MutableBorrow { .. }));
877    }
878
879    #[test]
880    fn effect_nodes() {
881        let handler = Box::new(make_node(
882            1,
883            NodeKind::Identifier {
884                name: dummy_ident("h"),
885            },
886        ));
887        let body = Box::new(make_node(
888            2,
889            NodeKind::Block {
890                stmts: vec![],
891                tail: None,
892            },
893        ));
894        let tp = TypePath {
895            segments: vec![dummy_ident("Log")],
896            span: dummy_span(),
897        };
898        let handling = make_node(
899            0,
900            NodeKind::HandlingBlock {
901                handlers: vec![AirHandlerPair {
902                    effect: tp.clone(),
903                    handler,
904                }],
905                body,
906            },
907        );
908        let effect_ref = make_node(3, NodeKind::EffectRef { path: tp });
909        assert!(matches!(handling.kind, NodeKind::HandlingBlock { .. }));
910        assert!(matches!(effect_ref.kind, NodeKind::EffectRef { .. }));
911    }
912
913    #[test]
914    fn type_expr_nodes() {
915        let named = make_node(
916            0,
917            NodeKind::TypeNamed {
918                path: TypePath {
919                    segments: vec![dummy_ident("Int")],
920                    span: dummy_span(),
921                },
922                args: vec![],
923            },
924        );
925        let self_ty = make_node(1, NodeKind::TypeSelf);
926        let opt = make_node(
927            2,
928            NodeKind::TypeOptional {
929                inner: Box::new(named.clone()),
930            },
931        );
932        assert!(matches!(named.kind, NodeKind::TypeNamed { .. }));
933        assert!(matches!(self_ty.kind, NodeKind::TypeSelf));
934        assert!(matches!(opt.kind, NodeKind::TypeOptional { .. }));
935    }
936
937    #[test]
938    fn metadata_and_effects_mutable() {
939        let mut node = make_node(0, NodeKind::Continue);
940        node.metadata
941            .insert("pass".into(), crate::stubs::Value::String("T-AIR".into()));
942        node.effects.insert(EffectRef::new("Std.Io.Log"));
943        node.capabilities
944            .insert(Capability::new("Std.Io.FileSystem"));
945        assert_eq!(node.metadata.len(), 1);
946        assert_eq!(node.effects.len(), 1);
947        assert_eq!(node.capabilities.len(), 1);
948    }
949}