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 target: Box<AIRNode>,
272 where_clause: Vec<TypeConstraint>,
273 methods: Vec<AIRNode>,
275 },
276
277 EffectDecl {
279 annotations: Vec<Annotation>,
280 visibility: Visibility,
281 name: Ident,
282 generic_params: Vec<GenericParam>,
283 components: Vec<TypePath>,
285 operations: Vec<AIRNode>,
287 },
288
289 TypeAlias {
291 annotations: Vec<Annotation>,
292 visibility: Visibility,
293 name: Ident,
294 generic_params: Vec<GenericParam>,
295 ty: Box<AIRNode>,
297 where_clause: Vec<TypeConstraint>,
298 },
299
300 ConstDecl {
302 annotations: Vec<Annotation>,
303 visibility: Visibility,
304 name: Ident,
305 ty: Box<AIRNode>,
307 value: Box<AIRNode>,
309 },
310
311 ModuleHandle {
313 effect: TypePath,
314 handler: Box<AIRNode>,
316 },
317
318 PropertyTest {
320 name: String,
321 bindings: Vec<PropertyBinding>,
322 body: Box<AIRNode>,
324 },
325
326 Param {
329 pattern: Box<AIRNode>,
331 ty: Option<Box<AIRNode>>,
333 default: Option<Box<AIRNode>>,
335 },
336
337 TypeNamed {
340 path: TypePath,
341 args: Vec<AIRNode>,
343 },
344
345 TypeTuple {
347 elems: Vec<AIRNode>,
349 },
350
351 TypeFunction {
353 params: Vec<AIRNode>,
355 ret: Box<AIRNode>,
357 effects: Vec<TypePath>,
359 },
360
361 TypeOptional {
363 inner: Box<AIRNode>,
365 },
366
367 TypeSelf,
369
370 Literal { lit: Literal },
373
374 Identifier { name: Ident },
376
377 BinaryOp {
379 op: BinOp,
380 left: Box<AIRNode>,
381 right: Box<AIRNode>,
382 },
383
384 UnaryOp { op: UnaryOp, operand: Box<AIRNode> },
386
387 Assign {
389 op: AssignOp,
390 target: Box<AIRNode>,
391 value: Box<AIRNode>,
392 },
393
394 Call {
396 callee: Box<AIRNode>,
397 args: Vec<AirArg>,
398 type_args: Vec<AIRNode>,
399 },
400
401 MethodCall {
403 receiver: Box<AIRNode>,
404 method: Ident,
405 type_args: Vec<AIRNode>,
406 args: Vec<AirArg>,
407 },
408
409 FieldAccess { object: Box<AIRNode>, field: Ident },
411
412 Index {
414 object: Box<AIRNode>,
415 index: Box<AIRNode>,
416 },
417
418 Propagate { expr: Box<AIRNode> },
420
421 Lambda {
423 params: Vec<AIRNode>,
425 body: Box<AIRNode>,
427 },
428
429 Pipe {
431 left: Box<AIRNode>,
432 right: Box<AIRNode>,
433 },
434
435 Compose {
437 left: Box<AIRNode>,
438 right: Box<AIRNode>,
439 },
440
441 Await { expr: Box<AIRNode> },
443
444 Range {
446 lo: Box<AIRNode>,
447 hi: Box<AIRNode>,
448 inclusive: bool,
449 },
450
451 RecordConstruct {
453 path: TypePath,
454 fields: Vec<AirRecordField>,
455 spread: Option<Box<AIRNode>>,
456 },
457
458 ListLiteral { elems: Vec<AIRNode> },
460
461 MapLiteral { entries: Vec<AirMapEntry> },
463
464 SetLiteral { elems: Vec<AIRNode> },
466
467 TupleLiteral { elems: Vec<AIRNode> },
469
470 Interpolation { parts: Vec<AirInterpolationPart> },
472
473 Placeholder,
475
476 Unreachable,
478
479 ResultConstruct {
481 variant: ResultVariant,
482 value: Option<Box<AIRNode>>,
483 },
484
485 If {
488 let_pattern: Option<Box<AIRNode>>,
490 condition: Box<AIRNode>,
491 then_block: Box<AIRNode>,
493 else_block: Option<Box<AIRNode>>,
495 },
496
497 Guard {
501 let_pattern: Option<Box<AIRNode>>,
503 condition: Box<AIRNode>,
504 else_block: Box<AIRNode>,
505 },
506
507 Match {
509 scrutinee: Box<AIRNode>,
510 arms: Vec<AIRNode>,
512 },
513
514 MatchArm {
516 pattern: Box<AIRNode>,
518 guard: Option<Box<AIRNode>>,
520 body: Box<AIRNode>,
522 },
523
524 For {
526 pattern: Box<AIRNode>,
528 iterable: Box<AIRNode>,
529 body: Box<AIRNode>,
530 },
531
532 While {
534 condition: Box<AIRNode>,
535 body: Box<AIRNode>,
536 },
537
538 Loop { body: Box<AIRNode> },
540
541 Block {
543 stmts: Vec<AIRNode>,
544 tail: Option<Box<AIRNode>>,
545 },
546
547 Return { value: Option<Box<AIRNode>> },
549
550 Break { value: Option<Box<AIRNode>> },
552
553 Continue,
555
556 LetBinding {
559 is_mut: bool,
560 pattern: Box<AIRNode>,
561 ty: Option<Box<AIRNode>>,
562 value: Box<AIRNode>,
563 },
564
565 Move { expr: Box<AIRNode> },
567
568 Borrow { expr: Box<AIRNode> },
570
571 MutableBorrow { expr: Box<AIRNode> },
573
574 EffectOp {
577 effect: TypePath,
578 operation: Ident,
579 args: Vec<AirArg>,
580 },
581
582 HandlingBlock {
584 handlers: Vec<AirHandlerPair>,
585 body: Box<AIRNode>,
586 },
587
588 EffectRef { path: TypePath },
590
591 WildcardPat,
594
595 BindPat { name: Ident, is_mut: bool },
597
598 LiteralPat { lit: Literal },
600
601 ConstructorPat {
603 path: TypePath,
604 fields: Vec<AIRNode>,
606 },
607
608 RecordPat {
610 path: TypePath,
611 fields: Vec<AirRecordPatternField>,
612 rest: bool,
614 },
615
616 TuplePat { elems: Vec<AIRNode> },
618
619 ListPat {
621 elems: Vec<AIRNode>,
622 rest: Option<Box<AIRNode>>,
623 },
624
625 OrPat { alternatives: Vec<AIRNode> },
627
628 GuardPat {
630 pattern: Box<AIRNode>,
631 guard: Box<AIRNode>,
632 },
633
634 RangePat {
636 lo: Box<AIRNode>,
637 hi: Box<AIRNode>,
638 inclusive: bool,
639 },
640
641 RestPat,
643
644 Error,
647}
648
649#[derive(Debug, Clone, PartialEq)]
651pub enum EnumVariantPayload {
652 Unit,
654 Struct(Vec<RecordDeclField>),
656 Tuple(Vec<AIRNode>),
658}
659
660#[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 #[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 assert_eq!(ids, vec![0, 1, 2, 3]);
715 }
716
717 #[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 #[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}