1use 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
21pub type NodeId = u32;
25
26#[derive(Debug, Default)]
31pub struct NodeIdGen {
32 counter: AtomicU32,
33}
34
35impl NodeIdGen {
36 #[must_use]
38 pub fn new() -> Self {
39 Self {
40 counter: AtomicU32::new(0),
41 }
42 }
43
44 #[must_use]
46 pub fn next(&self) -> NodeId {
47 self.counter.fetch_add(1, Ordering::SeqCst)
48 }
49}
50
51#[derive(Debug, Clone, PartialEq)]
61pub struct AIRNode {
62 pub id: NodeId,
64 pub span: Span,
66 pub kind: NodeKind,
68 pub type_info: Option<TypeInfo>,
71 pub ownership: Option<OwnershipInfo>,
73 pub effects: HashSet<EffectRef>,
75 pub capabilities: HashSet<Capability>,
77 pub context: Option<ContextBlock>,
80 pub target: Option<TargetInfo>,
83 pub metadata: HashMap<String, Value>,
86}
87
88impl AIRNode {
89 #[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#[derive(Debug, Clone, PartialEq)]
111pub struct AirArg {
112 pub label: Option<Ident>,
114 pub value: AIRNode,
116}
117
118#[derive(Debug, Clone, PartialEq)]
120pub struct AirRecordField {
121 pub name: Ident,
123 pub value: Option<Box<AIRNode>>,
125}
126
127#[derive(Debug, Clone, PartialEq)]
129pub struct AirRecordPatternField {
130 pub name: Ident,
132 pub pattern: Option<Box<AIRNode>>,
134}
135
136#[derive(Debug, Clone, PartialEq)]
138pub struct AirMapEntry {
139 pub key: AIRNode,
140 pub value: AIRNode,
141}
142
143#[derive(Debug, Clone, PartialEq)]
145pub struct AirHandlerPair {
146 pub effect: TypePath,
148 pub handler: Box<AIRNode>,
150}
151
152#[derive(Debug, Clone, PartialEq)]
154pub enum AirInterpolationPart {
155 Literal(String),
157 Expr(Box<AIRNode>),
159}
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq)]
163pub enum ResultVariant {
164 Ok,
165 Err,
166}
167
168#[derive(Debug, Clone, PartialEq)]
175#[non_exhaustive]
176pub enum NodeKind {
177 Module {
180 path: Option<ModulePath>,
181 annotations: Vec<Annotation>,
183 imports: Vec<AIRNode>,
185 items: Vec<AIRNode>,
187 },
188
189 ImportDecl {
191 path: ModulePath,
192 items: ImportItems,
193 },
194
195 FnDecl {
198 annotations: Vec<Annotation>,
199 visibility: Visibility,
200 is_async: bool,
201 name: Ident,
202 generic_params: Vec<GenericParam>,
203 params: Vec<AIRNode>,
205 return_type: Option<Box<AIRNode>>,
207 effect_clause: Vec<TypePath>,
209 where_clause: Vec<TypeConstraint>,
210 body: Box<AIRNode>,
212 },
213
214 RecordDecl {
216 annotations: Vec<Annotation>,
217 visibility: Visibility,
218 name: Ident,
219 generic_params: Vec<GenericParam>,
220 fields: Vec<RecordDeclField>,
221 },
222
223 EnumDecl {
225 annotations: Vec<Annotation>,
226 visibility: Visibility,
227 name: Ident,
228 generic_params: Vec<GenericParam>,
229 variants: Vec<AIRNode>,
231 },
232
233 EnumVariant {
235 name: Ident,
236 payload: EnumVariantPayload,
238 },
239
240 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 methods: Vec<AIRNode>,
251 },
252
253 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 methods: Vec<AIRNode>,
263 },
264
265 ImplBlock {
267 annotations: Vec<Annotation>,
268 generic_params: Vec<GenericParam>,
269 trait_path: Option<TypePath>,
270 trait_args: Vec<AIRNode>,
274 target: Box<AIRNode>,
276 where_clause: Vec<TypeConstraint>,
277 methods: Vec<AIRNode>,
279 },
280
281 EffectDecl {
283 annotations: Vec<Annotation>,
284 visibility: Visibility,
285 name: Ident,
286 generic_params: Vec<GenericParam>,
287 components: Vec<TypePath>,
289 operations: Vec<AIRNode>,
291 },
292
293 TypeAlias {
295 annotations: Vec<Annotation>,
296 visibility: Visibility,
297 name: Ident,
298 generic_params: Vec<GenericParam>,
299 ty: Box<AIRNode>,
301 where_clause: Vec<TypeConstraint>,
302 },
303
304 ConstDecl {
306 annotations: Vec<Annotation>,
307 visibility: Visibility,
308 name: Ident,
309 ty: Box<AIRNode>,
311 value: Box<AIRNode>,
313 },
314
315 ModuleHandle {
317 effect: TypePath,
318 handler: Box<AIRNode>,
320 },
321
322 PropertyTest {
324 name: String,
325 bindings: Vec<PropertyBinding>,
326 body: Box<AIRNode>,
328 },
329
330 Param {
333 pattern: Box<AIRNode>,
335 ty: Option<Box<AIRNode>>,
337 default: Option<Box<AIRNode>>,
339 },
340
341 TypeNamed {
344 path: TypePath,
345 args: Vec<AIRNode>,
347 },
348
349 TypeTuple {
351 elems: Vec<AIRNode>,
353 },
354
355 TypeFunction {
357 params: Vec<AIRNode>,
359 ret: Box<AIRNode>,
361 effects: Vec<TypePath>,
363 },
364
365 TypeOptional {
367 inner: Box<AIRNode>,
369 },
370
371 TypeSelf,
373
374 Literal { lit: Literal },
377
378 Identifier { name: Ident },
380
381 BinaryOp {
383 op: BinOp,
384 left: Box<AIRNode>,
385 right: Box<AIRNode>,
386 },
387
388 UnaryOp { op: UnaryOp, operand: Box<AIRNode> },
390
391 Assign {
393 op: AssignOp,
394 target: Box<AIRNode>,
395 value: Box<AIRNode>,
396 },
397
398 Call {
400 callee: Box<AIRNode>,
401 args: Vec<AirArg>,
402 type_args: Vec<AIRNode>,
403 },
404
405 MethodCall {
407 receiver: Box<AIRNode>,
408 method: Ident,
409 type_args: Vec<AIRNode>,
410 args: Vec<AirArg>,
411 },
412
413 FieldAccess { object: Box<AIRNode>, field: Ident },
415
416 Index {
418 object: Box<AIRNode>,
419 index: Box<AIRNode>,
420 },
421
422 Propagate { expr: Box<AIRNode> },
424
425 Lambda {
427 params: Vec<AIRNode>,
429 body: Box<AIRNode>,
431 },
432
433 Pipe {
435 left: Box<AIRNode>,
436 right: Box<AIRNode>,
437 },
438
439 Compose {
441 left: Box<AIRNode>,
442 right: Box<AIRNode>,
443 },
444
445 Await { expr: Box<AIRNode> },
447
448 Range {
450 lo: Box<AIRNode>,
451 hi: Box<AIRNode>,
452 inclusive: bool,
453 },
454
455 RecordConstruct {
457 path: TypePath,
458 fields: Vec<AirRecordField>,
459 spread: Option<Box<AIRNode>>,
460 },
461
462 ListLiteral { elems: Vec<AIRNode> },
464
465 MapLiteral { entries: Vec<AirMapEntry> },
467
468 SetLiteral { elems: Vec<AIRNode> },
470
471 TupleLiteral { elems: Vec<AIRNode> },
473
474 Interpolation { parts: Vec<AirInterpolationPart> },
476
477 Placeholder,
479
480 Unreachable,
482
483 ResultConstruct {
485 variant: ResultVariant,
486 value: Option<Box<AIRNode>>,
487 },
488
489 If {
492 let_pattern: Option<Box<AIRNode>>,
494 condition: Box<AIRNode>,
495 then_block: Box<AIRNode>,
497 else_block: Option<Box<AIRNode>>,
499 },
500
501 Guard {
505 let_pattern: Option<Box<AIRNode>>,
507 condition: Box<AIRNode>,
508 else_block: Box<AIRNode>,
509 },
510
511 Match {
513 scrutinee: Box<AIRNode>,
514 arms: Vec<AIRNode>,
516 },
517
518 MatchArm {
520 pattern: Box<AIRNode>,
522 guard: Option<Box<AIRNode>>,
524 body: Box<AIRNode>,
526 },
527
528 For {
530 pattern: Box<AIRNode>,
532 iterable: Box<AIRNode>,
533 body: Box<AIRNode>,
534 },
535
536 While {
538 condition: Box<AIRNode>,
539 body: Box<AIRNode>,
540 },
541
542 Loop { body: Box<AIRNode> },
544
545 Block {
547 stmts: Vec<AIRNode>,
548 tail: Option<Box<AIRNode>>,
549 },
550
551 Return { value: Option<Box<AIRNode>> },
553
554 Break { value: Option<Box<AIRNode>> },
556
557 Continue,
559
560 LetBinding {
563 is_mut: bool,
564 pattern: Box<AIRNode>,
565 ty: Option<Box<AIRNode>>,
566 value: Box<AIRNode>,
567 },
568
569 Move { expr: Box<AIRNode> },
571
572 Borrow { expr: Box<AIRNode> },
574
575 MutableBorrow { expr: Box<AIRNode> },
577
578 EffectOp {
581 effect: TypePath,
582 operation: Ident,
583 args: Vec<AirArg>,
584 },
585
586 HandlingBlock {
588 handlers: Vec<AirHandlerPair>,
589 body: Box<AIRNode>,
590 },
591
592 EffectRef { path: TypePath },
594
595 WildcardPat,
598
599 BindPat { name: Ident, is_mut: bool },
601
602 LiteralPat { lit: Literal },
604
605 ConstructorPat {
607 path: TypePath,
608 fields: Vec<AIRNode>,
610 },
611
612 RecordPat {
614 path: TypePath,
615 fields: Vec<AirRecordPatternField>,
616 rest: bool,
618 },
619
620 TuplePat { elems: Vec<AIRNode> },
622
623 ListPat {
625 elems: Vec<AIRNode>,
626 rest: Option<Box<AIRNode>>,
627 },
628
629 OrPat { alternatives: Vec<AIRNode> },
631
632 GuardPat {
634 pattern: Box<AIRNode>,
635 guard: Box<AIRNode>,
636 },
637
638 RangePat {
640 lo: Box<AIRNode>,
641 hi: Box<AIRNode>,
642 inclusive: bool,
643 },
644
645 RestPat,
647
648 Error,
651}
652
653#[derive(Debug, Clone, PartialEq)]
655pub enum EnumVariantPayload {
656 Unit,
658 Struct(Vec<RecordDeclField>),
660 Tuple(Vec<AIRNode>),
662}
663
664#[cfg(test)]
667mod tests {
668 use super::*;
669 use bock_errors::FileId;
670
671 fn dummy_span() -> Span {
672 Span {
673 file: FileId(0),
674 start: 0,
675 end: 0,
676 }
677 }
678
679 fn dummy_ident(name: &str) -> Ident {
680 Ident {
681 name: name.to_string(),
682 span: dummy_span(),
683 }
684 }
685
686 fn make_node(id: NodeId, kind: NodeKind) -> AIRNode {
687 AIRNode::new(id, dummy_span(), kind)
688 }
689
690 #[test]
693 fn node_id_gen_monotonic() {
694 let gen = NodeIdGen::new();
695 let a = gen.next();
696 let b = gen.next();
697 let c = gen.next();
698 assert_eq!(a, 0);
699 assert_eq!(b, 1);
700 assert_eq!(c, 2);
701 }
702
703 #[test]
704 fn node_id_gen_thread_safe() {
705 use std::sync::Arc;
706 use std::thread;
707
708 let gen = Arc::new(NodeIdGen::new());
709 let handles: Vec<_> = (0..4)
710 .map(|_| {
711 let g = Arc::clone(&gen);
712 thread::spawn(move || g.next())
713 })
714 .collect();
715 let mut ids: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
716 ids.sort();
717 assert_eq!(ids, vec![0, 1, 2, 3]);
719 }
720
721 #[test]
724 fn air_node_new_has_empty_slots() {
725 let node = make_node(42, NodeKind::Continue);
726 assert_eq!(node.id, 42);
727 assert!(node.type_info.is_none());
728 assert!(node.ownership.is_none());
729 assert!(node.effects.is_empty());
730 assert!(node.capabilities.is_empty());
731 assert!(node.context.is_none());
732 assert!(node.target.is_none());
733 assert!(node.metadata.is_empty());
734 }
735
736 #[test]
737 fn air_node_debug_contains_kind() {
738 let node = make_node(0, NodeKind::Unreachable);
739 let s = format!("{node:?}");
740 assert!(s.contains("Unreachable"));
741 }
742
743 #[test]
746 fn module_node() {
747 let n = make_node(
748 0,
749 NodeKind::Module {
750 path: None,
751 annotations: vec![],
752 imports: vec![],
753 items: vec![],
754 },
755 );
756 assert!(matches!(n.kind, NodeKind::Module { .. }));
757 }
758
759 #[test]
760 fn fn_decl_node() {
761 let body = make_node(
762 1,
763 NodeKind::Block {
764 stmts: vec![],
765 tail: None,
766 },
767 );
768 let n = make_node(
769 0,
770 NodeKind::FnDecl {
771 annotations: vec![],
772 visibility: Visibility::Public,
773 is_async: false,
774 name: dummy_ident("foo"),
775 generic_params: vec![],
776 params: vec![],
777 return_type: None,
778 effect_clause: vec![],
779 where_clause: vec![],
780 body: Box::new(body),
781 },
782 );
783 assert!(matches!(n.kind, NodeKind::FnDecl { .. }));
784 }
785
786 #[test]
787 fn binary_op_node() {
788 let left = make_node(
789 1,
790 NodeKind::Literal {
791 lit: Literal::Int("1".into()),
792 },
793 );
794 let right = make_node(
795 2,
796 NodeKind::Literal {
797 lit: Literal::Int("2".into()),
798 },
799 );
800 let n = make_node(
801 0,
802 NodeKind::BinaryOp {
803 op: BinOp::Add,
804 left: Box::new(left),
805 right: Box::new(right),
806 },
807 );
808 assert!(matches!(n.kind, NodeKind::BinaryOp { op: BinOp::Add, .. }));
809 }
810
811 #[test]
812 fn pattern_nodes() {
813 let wildcard = make_node(0, NodeKind::WildcardPat);
814 let bind = make_node(
815 1,
816 NodeKind::BindPat {
817 name: dummy_ident("x"),
818 is_mut: false,
819 },
820 );
821 let lit = make_node(
822 2,
823 NodeKind::LiteralPat {
824 lit: Literal::Bool(true),
825 },
826 );
827 assert!(matches!(wildcard.kind, NodeKind::WildcardPat));
828 assert!(matches!(bind.kind, NodeKind::BindPat { .. }));
829 assert!(matches!(lit.kind, NodeKind::LiteralPat { .. }));
830 }
831
832 #[test]
833 fn control_flow_nodes() {
834 let body = Box::new(make_node(
835 1,
836 NodeKind::Block {
837 stmts: vec![],
838 tail: None,
839 },
840 ));
841 let cond = Box::new(make_node(
842 2,
843 NodeKind::Literal {
844 lit: Literal::Bool(true),
845 },
846 ));
847
848 let while_node = make_node(
849 0,
850 NodeKind::While {
851 condition: cond.clone(),
852 body: body.clone(),
853 },
854 );
855 let loop_node = make_node(3, NodeKind::Loop { body: body.clone() });
856 let return_node = make_node(4, NodeKind::Return { value: None });
857 let break_node = make_node(5, NodeKind::Break { value: None });
858 let continue_node = make_node(6, NodeKind::Continue);
859
860 assert!(matches!(while_node.kind, NodeKind::While { .. }));
861 assert!(matches!(loop_node.kind, NodeKind::Loop { .. }));
862 assert!(matches!(return_node.kind, NodeKind::Return { value: None }));
863 assert!(matches!(break_node.kind, NodeKind::Break { value: None }));
864 assert!(matches!(continue_node.kind, NodeKind::Continue));
865 }
866
867 #[test]
868 fn ownership_nodes() {
869 let expr = Box::new(make_node(
870 1,
871 NodeKind::Identifier {
872 name: dummy_ident("x"),
873 },
874 ));
875 let mv = make_node(0, NodeKind::Move { expr: expr.clone() });
876 let borrow = make_node(2, NodeKind::Borrow { expr: expr.clone() });
877 let mut_borrow = make_node(3, NodeKind::MutableBorrow { expr: expr.clone() });
878 assert!(matches!(mv.kind, NodeKind::Move { .. }));
879 assert!(matches!(borrow.kind, NodeKind::Borrow { .. }));
880 assert!(matches!(mut_borrow.kind, NodeKind::MutableBorrow { .. }));
881 }
882
883 #[test]
884 fn effect_nodes() {
885 let handler = Box::new(make_node(
886 1,
887 NodeKind::Identifier {
888 name: dummy_ident("h"),
889 },
890 ));
891 let body = Box::new(make_node(
892 2,
893 NodeKind::Block {
894 stmts: vec![],
895 tail: None,
896 },
897 ));
898 let tp = TypePath {
899 segments: vec![dummy_ident("Log")],
900 span: dummy_span(),
901 };
902 let handling = make_node(
903 0,
904 NodeKind::HandlingBlock {
905 handlers: vec![AirHandlerPair {
906 effect: tp.clone(),
907 handler,
908 }],
909 body,
910 },
911 );
912 let effect_ref = make_node(3, NodeKind::EffectRef { path: tp });
913 assert!(matches!(handling.kind, NodeKind::HandlingBlock { .. }));
914 assert!(matches!(effect_ref.kind, NodeKind::EffectRef { .. }));
915 }
916
917 #[test]
918 fn type_expr_nodes() {
919 let named = make_node(
920 0,
921 NodeKind::TypeNamed {
922 path: TypePath {
923 segments: vec![dummy_ident("Int")],
924 span: dummy_span(),
925 },
926 args: vec![],
927 },
928 );
929 let self_ty = make_node(1, NodeKind::TypeSelf);
930 let opt = make_node(
931 2,
932 NodeKind::TypeOptional {
933 inner: Box::new(named.clone()),
934 },
935 );
936 assert!(matches!(named.kind, NodeKind::TypeNamed { .. }));
937 assert!(matches!(self_ty.kind, NodeKind::TypeSelf));
938 assert!(matches!(opt.kind, NodeKind::TypeOptional { .. }));
939 }
940
941 #[test]
942 fn metadata_and_effects_mutable() {
943 let mut node = make_node(0, NodeKind::Continue);
944 node.metadata
945 .insert("pass".into(), crate::stubs::Value::String("T-AIR".into()));
946 node.effects.insert(EffectRef::new("Std.Io.Log"));
947 node.capabilities
948 .insert(Capability::new("Std.Io.FileSystem"));
949 assert_eq!(node.metadata.len(), 1);
950 assert_eq!(node.effects.len(), 1);
951 assert_eq!(node.capabilities.len(), 1);
952 }
953}