1use std::collections::BTreeMap;
50use std::sync::Arc;
51
52use beck_diag::Span;
53
54use crate::check::Program;
55use crate::core::{Core, CoreKind};
56use crate::ty::{Effect, Tier, Ty, TyDecl};
57
58#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
60pub struct NodeId(pub u32);
61
62#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
63pub enum NodeKind {
64 Type,
66 Function,
68 Signal,
70 Resource,
72}
73
74impl NodeKind {
75 pub fn as_str(self) -> &'static str {
76 match self {
77 NodeKind::Type => "type",
78 NodeKind::Function => "function",
79 NodeKind::Signal => "signal",
80 NodeKind::Resource => "resource",
81 }
82 }
83}
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
86pub enum EdgeKind {
87 Calls,
89 Reads,
92 Uses,
94 Implies,
97 Needs,
100}
101
102impl EdgeKind {
103 pub fn as_str(self) -> &'static str {
104 match self {
105 EdgeKind::Calls => "calls",
106 EdgeKind::Reads => "reads",
107 EdgeKind::Uses => "uses",
108 EdgeKind::Implies => "implies",
109 EdgeKind::Needs => "needs",
110 }
111 }
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub struct Edge {
116 pub to: NodeId,
117 pub kind: EdgeKind,
118}
119
120#[derive(Clone, Debug, PartialEq, Eq)]
121pub struct GraphNode {
122 pub name: Arc<str>,
123 pub kind: NodeKind,
124 pub tier: Tier,
126 pub effects: Vec<Effect>,
127 pub because: String,
130 pub span: Span,
132}
133
134#[derive(Clone, Debug)]
136pub struct DepGraph {
137 nodes: Vec<GraphNode>,
138 by_name: BTreeMap<Arc<str>, NodeId>,
139 out_offsets: Vec<u32>,
140 out_edges: Vec<Edge>,
141 in_offsets: Vec<u32>,
142 in_edges: Vec<Edge>,
143 scc_of: Vec<u32>,
146 scc_members: Vec<NodeId>,
148 scc_offsets: Vec<u32>,
149 order: Vec<NodeId>,
152}
153
154impl DepGraph {
155 pub fn len(&self) -> usize {
156 self.nodes.len()
157 }
158
159 pub fn is_empty(&self) -> bool {
160 self.nodes.is_empty()
161 }
162
163 pub fn edge_count(&self) -> usize {
164 self.out_edges.len()
165 }
166
167 pub fn nodes(&self) -> impl Iterator<Item = (NodeId, &GraphNode)> {
168 self.nodes
169 .iter()
170 .enumerate()
171 .map(|(i, n)| (NodeId(i as u32), n))
172 }
173
174 pub fn node(&self, id: NodeId) -> &GraphNode {
175 &self.nodes[id.0 as usize]
176 }
177
178 pub fn id(&self, name: &str) -> Option<NodeId> {
179 self.by_name.get(name).copied()
180 }
181
182 pub fn dependencies(&self, id: NodeId) -> &[Edge] {
184 let i = id.0 as usize;
185 &self.out_edges[self.out_offsets[i] as usize..self.out_offsets[i + 1] as usize]
186 }
187
188 pub fn dependents(&self, id: NodeId) -> &[Edge] {
190 let i = id.0 as usize;
191 &self.in_edges[self.in_offsets[i] as usize..self.in_offsets[i + 1] as usize]
192 }
193
194 pub fn cycle_of(&self, id: NodeId) -> &[NodeId] {
197 self.members_of(self.scc_of[id.0 as usize])
198 }
199
200 fn members_of(&self, scc: u32) -> &[NodeId] {
201 let c = scc as usize;
202 &self.scc_members[self.scc_offsets[c] as usize..self.scc_offsets[c + 1] as usize]
203 }
204
205 pub fn scc_index(&self, id: NodeId) -> u32 {
207 self.scc_of[id.0 as usize]
208 }
209
210 pub fn cycles(&self) -> impl Iterator<Item = &[NodeId]> {
213 (0..self.scc_offsets.len() as u32 - 1)
214 .map(|c| self.members_of(c))
215 .filter(|m| m.len() > 1)
216 }
217
218 pub fn topological(&self) -> &[NodeId] {
220 &self.order
221 }
222
223 pub fn layers(&self) -> Vec<u32> {
232 let mut scc_layer = vec![0u32; self.scc_offsets.len() - 1];
233 for c in 0..scc_layer.len() as u32 {
236 let mut deepest = 0;
237 for &m in self.members_of(c) {
238 for e in self.dependencies(m) {
239 let d = self.scc_of[e.to.0 as usize];
240 if d != c {
241 deepest = deepest.max(scc_layer[d as usize] + 1);
242 }
243 }
244 }
245 scc_layer[c as usize] = deepest;
246 }
247 self.scc_of.iter().map(|c| scc_layer[*c as usize]).collect()
248 }
249
250 pub fn impacted_by(&self, id: NodeId) -> Vec<NodeId> {
253 self.impact(id).into_iter().map(|(n, _)| n).collect()
254 }
255
256 pub fn impact(&self, id: NodeId) -> Vec<(NodeId, u32)> {
262 let mut seen = vec![false; self.nodes.len()];
263 let mut queue = std::collections::VecDeque::from([(id, 0)]);
264 let mut out = Vec::new();
265 seen[id.0 as usize] = true;
266 while let Some((v, d)) = queue.pop_front() {
267 out.push((v, d));
268 for e in self.dependents(v) {
269 if !seen[e.to.0 as usize] {
270 seen[e.to.0 as usize] = true;
271 queue.push_back((e.to, d + 1));
272 }
273 }
274 }
275 out
276 }
277}
278
279#[derive(Default)]
288pub struct GraphBuilder {
289 nodes: Vec<GraphNode>,
290 by_name: BTreeMap<Arc<str>, NodeId>,
291 edges: Vec<(NodeId, Edge)>,
292}
293
294impl GraphBuilder {
295 pub fn new() -> GraphBuilder {
296 GraphBuilder::default()
297 }
298
299 pub fn node(&mut self, node: GraphNode) -> NodeId {
301 if let Some(id) = self.by_name.get(&node.name) {
302 return *id;
303 }
304 let id = NodeId(self.nodes.len() as u32);
305 self.by_name.insert(node.name.clone(), id);
306 self.nodes.push(node);
307 id
308 }
309
310 pub fn id(&self, name: &str) -> Option<NodeId> {
311 self.by_name.get(name).copied()
312 }
313
314 pub fn edge(&mut self, from: NodeId, to: NodeId, kind: EdgeKind) {
315 self.edges.push((from, Edge { to, kind }));
316 }
317
318 pub fn edge_to_name(&mut self, from: NodeId, to: &str, kind: EdgeKind) {
321 if let Some(to) = self.id(to) {
322 if to != from {
323 self.edge(from, to, kind);
324 }
325 }
326 }
327
328 pub fn finish(mut self) -> DepGraph {
330 let v = self.nodes.len();
331
332 self.edges
335 .sort_unstable_by_key(|(from, e)| (from.0, e.to.0, e.kind));
336 self.edges
337 .dedup_by_key(|(from, e)| (from.0, e.to.0, e.kind));
338
339 let (out_offsets, out_edges) = csr(v, self.edges.iter().map(|(f, e)| (*f, *e)));
340 let (in_offsets, in_edges) = csr(
341 v,
342 self.edges.iter().map(|(f, e)| {
343 (
344 e.to,
345 Edge {
346 to: *f,
347 kind: e.kind,
348 },
349 )
350 }),
351 );
352
353 let (scc_of, scc_members, scc_offsets) = tarjan(v, &out_offsets, &out_edges);
354 let order = scc_members.clone();
355
356 DepGraph {
357 nodes: self.nodes,
358 by_name: self.by_name,
359 out_offsets,
360 out_edges,
361 in_offsets,
362 in_edges,
363 scc_of,
364 scc_members,
365 scc_offsets,
366 order,
367 }
368 }
369}
370
371fn csr(v: usize, edges: impl Iterator<Item = (NodeId, Edge)> + Clone) -> (Vec<u32>, Vec<Edge>) {
374 let mut offsets = vec![0u32; v + 1];
375 for (from, _) in edges.clone() {
376 offsets[from.0 as usize + 1] += 1;
377 }
378 for i in 0..v {
379 offsets[i + 1] += offsets[i];
380 }
381 let mut out = vec![
382 Edge {
383 to: NodeId(0),
384 kind: EdgeKind::Calls
385 };
386 offsets[v] as usize
387 ];
388 let mut cursor = offsets.clone();
389 for (from, e) in edges {
390 let slot = &mut cursor[from.0 as usize];
391 out[*slot as usize] = e;
392 *slot += 1;
393 }
394 (offsets, out)
395}
396
397fn tarjan(v: usize, offsets: &[u32], edges: &[Edge]) -> (Vec<u32>, Vec<NodeId>, Vec<u32>) {
406 const UNVISITED: u32 = u32::MAX;
407
408 let mut index = vec![UNVISITED; v]; let mut low = vec![0u32; v];
410 let mut on_stack = vec![false; v];
411 let mut stack: Vec<NodeId> = Vec::new();
412 let mut next_index = 0u32;
413 let mut comps: Vec<Vec<NodeId>> = Vec::new();
415
416 let mut work: Vec<(u32, u32)> = Vec::new();
418
419 for root in 0..v as u32 {
420 if index[root as usize] != UNVISITED {
421 continue;
422 }
423 work.push((root, offsets[root as usize]));
424 index[root as usize] = next_index;
425 low[root as usize] = next_index;
426 next_index += 1;
427 stack.push(NodeId(root));
428 on_stack[root as usize] = true;
429
430 while let Some((node, edge_cursor)) = work.last_mut() {
431 let n = *node as usize;
432 if *edge_cursor < offsets[n + 1] {
433 let e = edges[*edge_cursor as usize];
434 *edge_cursor += 1;
435 let w = e.to.0 as usize;
436 if index[w] == UNVISITED {
437 index[w] = next_index;
438 low[w] = next_index;
439 next_index += 1;
440 stack.push(e.to);
441 on_stack[w] = true;
442 work.push((e.to.0, offsets[w]));
443 } else if on_stack[w] {
444 low[n] = low[n].min(index[w]);
445 }
446 } else {
447 if low[n] == index[n] {
449 let mut comp = Vec::new();
450 while let Some(w) = stack.pop() {
451 on_stack[w.0 as usize] = false;
452 comp.push(w);
453 if w.0 as usize == n {
454 break;
455 }
456 }
457 comps.push(comp);
458 }
459 work.pop();
460 if let Some((parent, _)) = work.last() {
461 let p = *parent as usize;
462 low[p] = low[p].min(low[n]);
463 }
464 }
465 }
466 }
467
468 let mut scc_of = vec![0u32; v];
469 let mut members = Vec::with_capacity(v);
470 let mut scc_offsets = Vec::with_capacity(comps.len() + 1);
471 scc_offsets.push(0);
472 for (c, comp) in comps.iter().enumerate() {
473 for &m in comp {
474 scc_of[m.0 as usize] = c as u32;
475 members.push(m);
476 }
477 scc_offsets.push(members.len() as u32);
478 }
479 (scc_of, members, scc_offsets)
480}
481
482pub fn from_program(program: &Program) -> GraphBuilder {
486 let mut b = GraphBuilder::new();
487
488 for name in program.types.keys() {
491 b.node(GraphNode {
492 name: name.clone(),
493 kind: NodeKind::Type,
494 tier: Tier::Any,
495 effects: Vec::new(),
496 because: String::new(),
497 span: Span::NONE,
500 });
501 }
502 for name in &program.def_order {
503 let Some(def) = program.defs.get(name) else {
504 continue;
505 };
506 b.node(GraphNode {
507 name: name.clone(),
508 kind: NodeKind::Function,
509 tier: def.tier,
510 effects: def.effects.clone(),
511 because: String::new(),
512 span: def.span,
513 });
514 }
515 for sig in &program.signals {
516 b.node(GraphNode {
517 name: sig.name.clone(),
518 kind: NodeKind::Signal,
519 tier: sig.tier,
520 effects: sig.effects.clone(),
521 because: String::new(),
522 span: sig.span,
523 });
524 }
525
526 for (name, decl) in &program.types {
529 let from = b.id(name).expect("just added");
530 match decl {
531 TyDecl::Model { fields, .. } => {
532 for (_, ty) in fields {
533 add_type_edges(&mut b, from, ty);
534 }
535 }
536 TyDecl::Union { variants, .. } => {
537 for v in variants {
538 for (_, ty) in &v.fields {
539 add_type_edges(&mut b, from, ty);
540 }
541 }
542 }
543 TyDecl::Newtype { inner: ty, .. } | TyDecl::Alias { ty, .. } => {
544 add_type_edges(&mut b, from, ty)
545 }
546 }
547 }
548
549 for name in &program.def_order {
550 let Some(def) = program.defs.get(name) else {
551 continue;
552 };
553 let from = b.id(name).expect("just added");
554 for (_, _, ty) in &def.params {
555 add_type_edges(&mut b, from, ty);
556 }
557 add_type_edges(&mut b, from, &def.ret);
558 add_body_edges(&mut b, from, &def.body, EdgeKind::Calls);
559 }
560 for sig in &program.signals {
561 let from = b.id(&sig.name).expect("just added");
562 add_type_edges(&mut b, from, &sig.ty);
563 add_body_edges(&mut b, from, &sig.expr, EdgeKind::Reads);
564 }
565 b
566}
567
568fn add_type_edges(b: &mut GraphBuilder, from: NodeId, ty: &Ty) {
570 match ty {
571 Ty::Con(name, args) => {
572 if b.id(name)
573 .is_some_and(|t| b.nodes[t.0 as usize].kind == NodeKind::Type)
574 {
575 b.edge_to_name(from, name, EdgeKind::Uses);
576 }
577 for a in args {
578 add_type_edges(b, from, a);
579 }
580 }
581 Ty::Fun(args, ret, _) => {
582 for a in args {
583 add_type_edges(b, from, a);
584 }
585 add_type_edges(b, from, ret);
586 }
587 Ty::Var(_) => {}
588 }
589}
590
591fn add_body_edges(b: &mut GraphBuilder, from: NodeId, core: &Core, default_kind: EdgeKind) {
596 walk(core, &mut |c| match &c.kind {
597 CoreKind::Global(name) => {
598 let kind = match b.id(name).map(|id| b.nodes[id.0 as usize].kind) {
599 Some(NodeKind::Signal) => EdgeKind::Reads,
600 Some(NodeKind::Type) => EdgeKind::Uses,
601 _ => default_kind,
602 };
603 b.edge_to_name(from, name, kind);
604 }
605 CoreKind::Make { ty, .. } => b.edge_to_name(from, ty, EdgeKind::Uses),
606 _ => {}
607 });
608}
609
610fn walk(core: &Core, f: &mut impl FnMut(&Core)) {
612 f(core);
613 match &core.kind {
614 CoreKind::Const(_) | CoreKind::Var(_) | CoreKind::Global(_) => {}
615 CoreKind::Lam { body, .. } => walk(body, f),
616 CoreKind::App { func, args } => {
617 walk(func, f);
618 args.iter().for_each(|a| walk(a, f));
619 }
620 CoreKind::Prim { args, .. } => args.iter().for_each(|a| walk(a, f)),
621 CoreKind::Let { value, body, .. } => {
622 walk(value, f);
623 walk(body, f);
624 }
625 CoreKind::If { cond, then, alt } => {
626 walk(cond, f);
627 walk(then, f);
628 walk(alt, f);
629 }
630 CoreKind::Match { scrutinee, arms } => {
631 walk(scrutinee, f);
632 arms.iter().flat_map(|a| a.exprs()).for_each(|e| walk(e, f));
633 }
634 CoreKind::Make { fields, .. } => {
635 fields.iter().for_each(|(_, v)| walk(v, f));
636 }
637 CoreKind::With { base, fields } => {
639 walk(base, f);
640 fields.iter().for_each(|(_, v)| walk(v, f));
641 }
642 CoreKind::Field { base, .. } => walk(base, f),
643 CoreKind::ListLit(xs) => xs.iter().for_each(|x| walk(x, f)),
644 CoreKind::MapLit(kvs) => kvs.iter().for_each(|(k, v)| {
645 walk(k, f);
646 walk(v, f);
647 }),
648 }
649}
650
651#[cfg(test)]
652mod tests {
653 use super::*;
654
655 fn graph_of(names: &[&str], edges: &[(&str, &str)]) -> DepGraph {
658 let mut b = GraphBuilder::new();
659 for n in names {
660 b.node(GraphNode {
661 name: Arc::from(*n),
662 kind: NodeKind::Function,
663 tier: Tier::Any,
664 effects: Vec::new(),
665 because: String::new(),
666 span: Span::NONE,
667 });
668 }
669 for (f, t) in edges {
670 let (f, t) = (b.id(f).unwrap(), b.id(t).unwrap());
671 b.edge(f, t, EdgeKind::Calls);
672 }
673 b.finish()
674 }
675
676 #[test]
677 fn edges_go_both_ways_and_duplicates_collapse() {
678 let g = graph_of(&["a", "b", "c"], &[("a", "b"), ("a", "b"), ("c", "b")]);
679 let b = g.id("b").unwrap();
680 assert_eq!(
681 g.dependencies(g.id("a").unwrap()).len(),
682 1,
683 "duplicate not collapsed"
684 );
685 assert_eq!(g.dependencies(b).len(), 0);
686 assert_eq!(g.dependents(b).len(), 2);
687 assert_eq!(g.edge_count(), 2);
688 }
689
690 #[test]
691 fn a_cycle_becomes_one_component_rather_than_a_failure() {
692 let g = graph_of(
694 &["merge", "events", "todos", "page", "unrelated"],
695 &[
696 ("events", "merge"),
697 ("events", "todos"),
698 ("todos", "events"),
699 ("page", "todos"),
700 ],
701 );
702 let cycles: Vec<Vec<&str>> = g
703 .cycles()
704 .map(|c| {
705 let mut names: Vec<&str> = c.iter().map(|n| &*g.node(*n).name).collect();
706 names.sort_unstable();
707 names
708 })
709 .collect();
710 assert_eq!(cycles, vec![vec!["events", "todos"]]);
711 assert_eq!(g.cycle_of(g.id("page").unwrap()).len(), 1, "not in a cycle");
712 assert_eq!(g.cycle_of(g.id("unrelated").unwrap()).len(), 1);
713 }
714
715 #[test]
716 fn the_condensation_is_topologically_ordered() {
717 let g = graph_of(
718 &["merge", "events", "todos", "page"],
719 &[
720 ("events", "merge"),
721 ("events", "todos"),
722 ("todos", "events"),
723 ("page", "todos"),
724 ],
725 );
726 let scc = |n: &str| g.scc_index(g.id(n).unwrap());
727 assert!(
729 scc("merge") < scc("events"),
730 "dependency must precede dependent"
731 );
732 assert_eq!(
733 scc("events"),
734 scc("todos"),
735 "cycle members share a component"
736 );
737 assert!(scc("todos") < scc("page"));
738
739 let order: Vec<&str> = g.topological().iter().map(|n| &*g.node(*n).name).collect();
741 let at = |n: &str| order.iter().position(|x| *x == n).unwrap();
742 assert!(at("merge") < at("events"));
743 assert!(at("todos") < at("page"));
744 assert_eq!(order.len(), 4);
745 }
746
747 #[test]
748 fn impact_is_the_transitive_dependents_and_stops_there() {
749 let g = graph_of(
750 &["util", "a", "b", "unrelated", "other"],
751 &[("a", "util"), ("b", "a"), ("other", "unrelated")],
752 );
753 let mut impacted: Vec<&str> = g
754 .impacted_by(g.id("util").unwrap())
755 .iter()
756 .map(|n| &*g.node(*n).name)
757 .collect();
758 impacted.sort_unstable();
759 assert_eq!(impacted, vec!["a", "b", "util"]);
760
761 assert_eq!(g.impacted_by(g.id("other").unwrap()).len(), 1);
763
764 let hops: Vec<(&str, u32)> = g
766 .impact(g.id("util").unwrap())
767 .iter()
768 .map(|(n, d)| (&*g.node(*n).name, *d))
769 .collect();
770 assert_eq!(hops, vec![("util", 0), ("a", 1), ("b", 2)]);
771 }
772
773 #[test]
774 fn a_cycle_reached_from_outside_pulls_in_the_whole_component() {
775 let g = graph_of(&["x", "y", "z"], &[("x", "y"), ("y", "x"), ("z", "x")]);
776 assert_eq!(g.impacted_by(g.id("y").unwrap()).len(), 3);
777 }
778
779 #[test]
780 fn layers_put_dependencies_to_the_left_and_cycles_in_one_column() {
781 let g = graph_of(
782 &["merge", "events", "todos", "page", "loner"],
783 &[
784 ("events", "merge"),
785 ("events", "todos"),
786 ("todos", "events"),
787 ("page", "todos"),
788 ],
789 );
790 let layers = g.layers();
791 let l = |n: &str| layers[g.id(n).unwrap().0 as usize];
792 assert_eq!(l("merge"), 0, "depends on nothing");
793 assert_eq!(l("loner"), 0);
794 assert_eq!(l("events"), 1);
795 assert_eq!(l("todos"), 1, "a cycle is one column, not two");
796 assert_eq!(l("page"), 2);
797 }
798
799 #[test]
800 fn deep_chains_do_not_overflow_the_stack() {
801 let names: Vec<String> = (0..100_000).map(|i| format!("n{i}")).collect();
803 let mut b = GraphBuilder::new();
804 for n in &names {
805 b.node(GraphNode {
806 name: Arc::from(n.as_str()),
807 kind: NodeKind::Function,
808 tier: Tier::Any,
809 effects: Vec::new(),
810 because: String::new(),
811 span: Span::NONE,
812 });
813 }
814 for i in 0..names.len() - 1 {
815 b.edge(NodeId(i as u32), NodeId(i as u32 + 1), EdgeKind::Calls);
816 }
817 let g = b.finish();
818 assert_eq!(g.len(), 100_000);
819 assert_eq!(g.cycles().count(), 0);
820 assert_eq!(g.impacted_by(NodeId(99_999)).len(), 100_000);
822 }
823
824 #[test]
825 fn a_walk_reaches_the_base_of_a_with() {
826 let global =
828 |name: &str| Core::new(CoreKind::Global(Arc::from(name)), Ty::unit(), Span::NONE);
829 let with = Core::new(
830 CoreKind::With {
831 base: Box::new(global("through_the_base")),
832 fields: vec![(Arc::from("f"), global("through_a_field"))],
833 },
834 Ty::unit(),
835 Span::NONE,
836 );
837 let mut seen = Vec::new();
838 walk(&with, &mut |c| {
839 if let CoreKind::Global(name) = &c.kind {
840 seen.push(name.to_string());
841 }
842 });
843 assert_eq!(seen, ["through_the_base", "through_a_field"]);
844 }
845}