1use std::borrow::Cow;
4use std::collections::btree_map::Entry;
5use std::collections::{BTreeMap, BTreeSet};
6
7use itertools::Itertools;
8use proc_macro2::Span;
9use quote::ToTokens;
10use syn::spanned::Spanned;
11use syn::{Error, Ident, ItemUse};
12
13use super::ops::next_iteration::NEXT_ITERATION;
14use super::ops::{FloType, Persistence};
15use super::{DfirGraph, GraphEdgeId, GraphLoopId, GraphNode, GraphNodeId, PortIndexValue};
16use crate::diagnostic::{Diagnostic, Diagnostics, Level};
17use crate::graph::graph_algorithms;
18use crate::graph::ops::{PortListSpec, RangeTrait};
19use crate::parse::{DfirCode, DfirStatement, Operator, Pipeline};
20use crate::pretty_span::PrettySpan;
21
22#[derive(Clone, Debug)]
23struct Ends {
24 inn: Option<(PortIndexValue, GraphDet)>,
25 out: Option<(PortIndexValue, GraphDet)>,
26}
27
28#[derive(Clone, Debug)]
29enum GraphDet {
30 Determined(GraphNodeId),
31 Undetermined(Ident),
32}
33
34#[derive(Debug)]
36struct VarnameInfo {
37 pub ends: Ends,
39 pub illegal_cycle: bool,
41 pub inn_used: bool,
43 pub out_used: bool,
45}
46impl VarnameInfo {
47 pub fn new(ends: Ends) -> Self {
48 Self {
49 ends,
50 illegal_cycle: false,
51 inn_used: false,
52 out_used: false,
53 }
54 }
55}
56
57#[derive(Debug, Default)]
59pub struct FlatGraphBuilder {
60 diagnostics: Diagnostics,
62
63 flat_graph: DfirGraph,
65 varname_ends: BTreeMap<Ident, VarnameInfo>,
67 links: Vec<Ends>,
69
70 uses: Vec<ItemUse>,
72
73 module_boundary_nodes: Option<(GraphNodeId, GraphNodeId)>,
76}
77
78pub struct FlatGraphBuilderOutput {
80 pub flat_graph: DfirGraph,
82 pub uses: Vec<ItemUse>,
84 pub diagnostics: Diagnostics,
86}
87
88impl FlatGraphBuilder {
89 pub fn new() -> Self {
91 Default::default()
92 }
93
94 pub fn from_dfir(input: DfirCode) -> Self {
96 let mut builder = Self::default();
97 builder.add_dfir(input, None, None);
98 builder
99 }
100
101 pub fn build(mut self) -> Result<FlatGraphBuilderOutput, Diagnostics> {
106 self.finalize_connect_operator_links();
107 self.process_operator_errors();
108
109 if self.diagnostics.has_error() {
110 Err(self.diagnostics)
111 } else {
112 Ok(FlatGraphBuilderOutput {
113 flat_graph: self.flat_graph,
114 uses: self.uses,
115 diagnostics: self.diagnostics,
116 })
117 }
118 }
119
120 pub fn add_dfir(
126 &mut self,
127 dfir: DfirCode,
128 current_loop: Option<GraphLoopId>,
129 operator_tag: Option<&str>,
130 ) {
131 for stmt in dfir.statements {
132 self.add_statement_internal(stmt, current_loop, operator_tag);
133 }
134 }
135
136 pub fn add_statement(&mut self, stmt: DfirStatement) {
138 self.add_statement_internal(stmt, None, None);
139 }
140
141 fn add_statement_internal(
147 &mut self,
148 stmt: DfirStatement,
149 current_loop: Option<GraphLoopId>,
150 operator_tag: Option<&str>,
151 ) {
152 match stmt {
153 DfirStatement::Use(yuse) => {
154 self.uses.push(yuse);
155 }
156 DfirStatement::Named(named) => {
157 let stmt_span = named.span();
158 let ends = self.add_pipeline(
159 named.pipeline,
160 Some(&named.name),
161 current_loop,
162 operator_tag,
163 );
164 self.assign_varname_checked(named.name, stmt_span, ends);
165 }
166 DfirStatement::Pipeline(pipeline_stmt) => {
167 let ends =
168 self.add_pipeline(pipeline_stmt.pipeline, None, current_loop, operator_tag);
169 Self::helper_check_unused_port(&mut self.diagnostics, &ends, true);
170 Self::helper_check_unused_port(&mut self.diagnostics, &ends, false);
171 }
172 DfirStatement::Loop(loop_statement) => {
173 let inner_loop = self.flat_graph.insert_loop(current_loop);
174 for stmt in loop_statement.statements {
175 self.add_statement_internal(stmt, Some(inner_loop), operator_tag);
176 }
177 }
178 }
179 }
180
181 pub fn append_assign_pipeline(
193 &mut self,
194 asgn_name: Option<&Ident>,
195 pred_name: Option<&Ident>,
196 pipeline: Pipeline,
197 current_loop: Option<GraphLoopId>,
198 operator_tag: Option<&str>,
199 ) {
200 let span = pipeline.span();
201 let mut ends = self.add_pipeline(pipeline, asgn_name, current_loop, operator_tag);
202
203 if let Some(pred_name) = pred_name {
205 if let Some(pred_varname_info) = self.varname_ends.get(pred_name) {
206 ends = self.connect_ends(pred_varname_info.ends.clone(), ends);
208 } else {
209 self.diagnostics.push(Diagnostic::spanned(
210 pred_name.span(),
211 Level::Error,
212 format!(
213 "Cannot find referenced name `{}`; name was never assigned.",
214 pred_name
215 ),
216 ));
217 }
218 }
219
220 if let Some(asgn_name) = asgn_name {
222 self.assign_varname_checked(asgn_name.clone(), span, ends);
223 }
224 }
225}
226
227impl FlatGraphBuilder {
229 fn assign_varname_checked(&mut self, name: Ident, stmt_span: Span, ends: Ends) {
231 match self.varname_ends.entry(name) {
232 Entry::Vacant(vacant_entry) => {
233 vacant_entry.insert(VarnameInfo::new(ends));
234 }
235 Entry::Occupied(occupied_entry) => {
236 let prev_conflict = occupied_entry.key();
237 self.diagnostics.push(Diagnostic::spanned(
238 prev_conflict.span(),
239 Level::Error,
240 format!(
241 "Existing assignment to `{}` conflicts with later assignment: {} (1/2)",
242 prev_conflict,
243 PrettySpan(stmt_span),
244 ),
245 ));
246 self.diagnostics.push(Diagnostic::spanned(
247 stmt_span,
248 Level::Error,
249 format!(
250 "Name assignment to `{}` conflicts with existing assignment: {} (2/2)",
251 prev_conflict,
252 PrettySpan(prev_conflict.span())
253 ),
254 ));
255 }
256 }
257 }
258
259 fn add_pipeline(
261 &mut self,
262 pipeline: Pipeline,
263 current_varname: Option<&Ident>,
264 current_loop: Option<GraphLoopId>,
265 operator_tag: Option<&str>,
266 ) -> Ends {
267 match pipeline {
268 Pipeline::Paren(ported_pipeline_paren) => {
269 let (inn_port, pipeline_paren, out_port) =
270 PortIndexValue::from_ported(ported_pipeline_paren);
271 let og_ends = self.add_pipeline(
272 *pipeline_paren.pipeline,
273 current_varname,
274 current_loop,
275 operator_tag,
276 );
277 Self::helper_combine_ends(&mut self.diagnostics, og_ends, inn_port, out_port)
278 }
279 Pipeline::Name(pipeline_name) => {
280 let (inn_port, ident, out_port) = PortIndexValue::from_ported(pipeline_name);
281
282 Ends {
285 inn: Some((inn_port, GraphDet::Undetermined(ident.clone()))),
286 out: Some((out_port, GraphDet::Undetermined(ident))),
287 }
288 }
289 Pipeline::ModuleBoundary(pipeline_name) => {
290 let Some((input_node, output_node)) = self.module_boundary_nodes else {
291 self.diagnostics.push(
292 Error::new(
293 pipeline_name.span(),
294 "`mod` is only usable inside of a module.",
295 )
296 .into(),
297 );
298
299 return Ends {
300 inn: None,
301 out: None,
302 };
303 };
304
305 let (inn_port, _, out_port) = PortIndexValue::from_ported(pipeline_name);
306
307 Ends {
308 inn: Some((inn_port, GraphDet::Determined(output_node))),
309 out: Some((out_port, GraphDet::Determined(input_node))),
310 }
311 }
312 Pipeline::Link(pipeline_link) => {
313 let lhs_ends = self.add_pipeline(
315 *pipeline_link.lhs,
316 current_varname,
317 current_loop,
318 operator_tag,
319 );
320 let rhs_ends = self.add_pipeline(
321 *pipeline_link.rhs,
322 current_varname,
323 current_loop,
324 operator_tag,
325 );
326
327 self.connect_ends(lhs_ends, rhs_ends)
328 }
329 Pipeline::Operator(operator) => {
330 let op_span = Some(operator.span());
331 let (node_id, ends) =
332 self.add_operator(current_varname, current_loop, operator, op_span);
333 if let Some(operator_tag) = operator_tag {
334 self.flat_graph
335 .set_operator_tag(node_id, operator_tag.to_owned());
336 }
337 ends
338 }
339 }
340 }
341
342 fn connect_ends(&mut self, lhs_ends: Ends, rhs_ends: Ends) -> Ends {
346 let outer_ends = Ends {
348 inn: lhs_ends.inn,
349 out: rhs_ends.out,
350 };
351 let link_ends = Ends {
353 out: lhs_ends.out,
354 inn: rhs_ends.inn,
355 };
356 self.links.push(link_ends);
357 outer_ends
358 }
359
360 fn add_operator(
362 &mut self,
363 current_varname: Option<&Ident>,
364 current_loop: Option<GraphLoopId>,
365 operator: Operator,
366 op_span: Option<Span>,
367 ) -> (GraphNodeId, Ends) {
368 let node_id = self.flat_graph.insert_node(
369 GraphNode::Operator(operator),
370 current_varname.cloned(),
371 current_loop,
372 );
373 let ends = Ends {
374 inn: Some((
375 PortIndexValue::Elided(op_span),
376 GraphDet::Determined(node_id),
377 )),
378 out: Some((
379 PortIndexValue::Elided(op_span),
380 GraphDet::Determined(node_id),
381 )),
382 };
383 (node_id, ends)
384 }
385
386 fn finalize_connect_operator_links(&mut self) {
389 for Ends { out, inn } in std::mem::take(&mut self.links) {
391 let out_opt = self.helper_resolve_name(out, false);
392 let inn_opt = self.helper_resolve_name(inn, true);
393 if let (Some((out_port, out_node)), Some((inn_port, inn_node))) = (out_opt, inn_opt) {
395 let _ = self.finalize_connect_operators(out_port, out_node, inn_port, inn_node);
396 }
397 }
398
399 for node_id in self.flat_graph.node_ids().collect::<Vec<_>>() {
401 if let GraphNode::Operator(operator) = self.flat_graph.node(node_id) {
402 let singletons_referenced = operator
403 .singletons_referenced
404 .clone()
405 .into_iter()
406 .map(|singleton_ref| {
407 let port_det = self
408 .varname_ends
409 .get(&singleton_ref)
410 .filter(|varname_info| !varname_info.illegal_cycle)
411 .map(|varname_info| &varname_info.ends)
412 .and_then(|ends| ends.out.as_ref())
413 .cloned();
414 if let Some((_port, node_id)) = self.helper_resolve_name(port_det, false) {
415 Some(node_id)
416 } else {
417 self.diagnostics.push(Diagnostic::spanned(
418 singleton_ref.span(),
419 Level::Error,
420 format!(
421 "Cannot find referenced name `{}`; name was never assigned.",
422 singleton_ref
423 ),
424 ));
425 None
426 }
427 })
428 .collect();
429
430 self.flat_graph
431 .set_node_singleton_references(node_id, singletons_referenced);
432 }
433 }
434 }
435
436 fn helper_resolve_name(
443 &mut self,
444 mut port_det: Option<(PortIndexValue, GraphDet)>,
445 is_in: bool,
446 ) -> Option<(PortIndexValue, GraphNodeId)> {
447 const BACKUP_RECURSION_LIMIT: usize = 1024;
448
449 let mut names = Vec::new();
450 for _ in 0..BACKUP_RECURSION_LIMIT {
451 match port_det? {
452 (port, GraphDet::Determined(node_id)) => {
453 return Some((port, node_id));
454 }
455 (port, GraphDet::Undetermined(ident)) => {
456 let Some(varname_info) = self.varname_ends.get_mut(&ident) else {
457 self.diagnostics.push(Diagnostic::spanned(
458 ident.span(),
459 Level::Error,
460 format!("Cannot find name `{}`; name was never assigned.", ident),
461 ));
462 return None;
463 };
464 let cycle_found = names.contains(&ident);
466 if !cycle_found {
467 names.push(ident);
468 };
469 if cycle_found || varname_info.illegal_cycle {
470 let len = names.len();
471 for (i, name) in names.into_iter().enumerate() {
472 self.diagnostics.push(Diagnostic::spanned(
473 name.span(),
474 Level::Error,
475 format!(
476 "Name `{}` forms or references an illegal self-referential cycle ({}/{}).",
477 name,
478 i + 1,
479 len
480 ),
481 ));
482 self.varname_ends.get_mut(&name).unwrap().illegal_cycle = true;
485 }
486 return None;
487 }
488
489 let prev = if is_in {
491 varname_info.inn_used = true;
492 &varname_info.ends.inn
493 } else {
494 varname_info.out_used = true;
495 &varname_info.ends.out
496 };
497 port_det = Self::helper_combine_end(
498 &mut self.diagnostics,
499 prev.clone(),
500 port,
501 if is_in { "input" } else { "output" },
502 );
503 }
504 }
505 }
506 self.diagnostics.push(Diagnostic::spanned(
507 Span::call_site(),
508 Level::Error,
509 format!(
510 "Reached the recursion limit {} while resolving names. This is either a dfir bug or you have an absurdly long chain of names: `{}`.",
511 BACKUP_RECURSION_LIMIT,
512 names.iter().map(ToString::to_string).collect::<Vec<_>>().join("` -> `"),
513 )
514 ));
515 None
516 }
517
518 fn finalize_connect_operators(
520 &mut self,
521 src_port: PortIndexValue,
522 src: GraphNodeId,
523 dst_port: PortIndexValue,
524 dst: GraphNodeId,
525 ) -> GraphEdgeId {
526 {
527 fn emit_conflict(
529 inout: &str,
530 old: &PortIndexValue,
531 new: &PortIndexValue,
532 diagnostics: &mut Diagnostics,
533 ) {
534 diagnostics.push(Diagnostic::spanned(
536 old.span(),
537 Level::Error,
538 format!(
539 "{} connection conflicts with below ({}) (1/2)",
540 inout,
541 PrettySpan(new.span()),
542 ),
543 ));
544 diagnostics.push(Diagnostic::spanned(
545 new.span(),
546 Level::Error,
547 format!(
548 "{} connection conflicts with above ({}) (2/2)",
549 inout,
550 PrettySpan(old.span()),
551 ),
552 ));
553 }
554
555 if src_port.is_specified() {
557 for conflicting_port in self
558 .flat_graph
559 .node_successor_edges(src)
560 .map(|edge_id| self.flat_graph.edge_ports(edge_id).0)
561 .filter(|&port| port == &src_port)
562 {
563 emit_conflict("Output", conflicting_port, &src_port, &mut self.diagnostics);
564 }
565 }
566
567 if dst_port.is_specified() {
569 for conflicting_port in self
570 .flat_graph
571 .node_predecessor_edges(dst)
572 .map(|edge_id| self.flat_graph.edge_ports(edge_id).1)
573 .filter(|&port| port == &dst_port)
574 {
575 emit_conflict("Input", conflicting_port, &dst_port, &mut self.diagnostics);
576 }
577 }
578 }
579 self.flat_graph.insert_edge(src, src_port, dst, dst_port)
580 }
581
582 fn process_operator_errors(&mut self) {
584 self.make_operator_instances();
585 self.flat_graph.compute_node_singletons();
586 self.check_operator_errors();
587 self.warn_unused_port_indexing();
588 self.check_loop_errors();
589 }
590
591 fn make_operator_instances(&mut self) {
593 self.flat_graph
594 .insert_node_op_insts_all(&mut self.diagnostics);
595 }
596
597 fn check_operator_errors(&mut self) {
600 for (node_id, node) in self.flat_graph.nodes() {
601 match node {
602 GraphNode::Operator(operator) => {
603 let Some(op_inst) = self.flat_graph.node_op_inst(node_id) else {
604 continue;
606 };
607 let op_constraints = op_inst.op_constraints;
608 let op_name = operator.name_string();
609
610 if op_constraints.num_args != operator.args.len() {
612 self.diagnostics.push(Diagnostic::spanned(
613 operator.span(),
614 Level::Error,
615 format!(
616 "`{}` expects {} argument(s), received {}.",
617 op_name,
618 op_constraints.num_args,
619 operator.args.len()
620 ),
621 ));
622 }
623
624 fn emit_arity_error(
627 op_span: Span,
628 op_name: &str,
629 is_in: bool,
630 is_hard: bool,
631 degree: usize,
632 range: &dyn RangeTrait<usize>,
633 diagnostics: &mut Diagnostics,
634 ) -> bool {
635 let message = format!(
636 "`{}` {} have {} {}, actually has {}.",
637 op_name,
638 if is_hard { "must" } else { "should" },
639 range.human_string(),
640 if is_in { "input(s)" } else { "output(s)" },
641 degree,
642 );
643 let out_of_range = !range.contains(°ree);
644 if out_of_range {
645 diagnostics.push(Diagnostic::spanned(
646 op_span,
647 if is_hard {
648 Level::Error
649 } else {
650 Level::Warning
651 },
652 message,
653 ));
654 }
655 out_of_range
656 }
657
658 let inn_degree = self.flat_graph.node_degree_in(node_id);
659 let _ = emit_arity_error(
660 operator.span(),
661 &op_name,
662 true,
663 true,
664 inn_degree,
665 op_constraints.hard_range_inn,
666 &mut self.diagnostics,
667 ) || emit_arity_error(
668 operator.span(),
669 &op_name,
670 true,
671 false,
672 inn_degree,
673 op_constraints.soft_range_inn,
674 &mut self.diagnostics,
675 );
676
677 let out_degree = self.flat_graph.node_degree_out(node_id);
678 let _ = emit_arity_error(
679 operator.span(),
680 &op_name,
681 false,
682 true,
683 out_degree,
684 op_constraints.hard_range_out,
685 &mut self.diagnostics,
686 ) || emit_arity_error(
687 operator.span(),
688 &op_name,
689 false,
690 false,
691 out_degree,
692 op_constraints.soft_range_out,
693 &mut self.diagnostics,
694 );
695
696 fn emit_port_error<'a>(
697 op_span: Span,
698 op_name: &str,
699 expected_ports_fn: Option<fn() -> PortListSpec>,
700 actual_ports_iter: impl Iterator<Item = &'a PortIndexValue>,
701 input_output: &'static str,
702 diagnostics: &mut Diagnostics,
703 ) {
704 let Some(expected_ports_fn) = expected_ports_fn else {
705 return;
706 };
707 let PortListSpec::Fixed(expected_ports) = (expected_ports_fn)() else {
708 return;
710 };
711 let expected_ports: Vec<_> = expected_ports.into_iter().collect();
712
713 let ports: BTreeSet<_> = actual_ports_iter
715 .inspect(|actual_port_iv| {
718 let is_expected = expected_ports.iter().any(|port_index| {
720 actual_port_iv == &&port_index.clone().into()
721 });
722 if !is_expected {
724 diagnostics.push(Diagnostic::spanned(
725 actual_port_iv.span(),
726 Level::Error,
727 format!(
728 "`{}` received unexpected {} port: {}. Expected one of: `{}`",
729 op_name,
730 input_output,
731 actual_port_iv.as_error_message_string(),
732 Itertools::intersperse(
733 expected_ports
734 .iter()
735 .map(|port| port.to_token_stream().to_string())
736 .map(Cow::Owned),
737 Cow::Borrowed("`, `"),
738 ).collect::<String>()
739 ),
740 ))
741 }
742 })
743 .collect();
744
745 let missing: Vec<_> = expected_ports
747 .into_iter()
748 .filter_map(|expected_port| {
749 let tokens = expected_port.to_token_stream();
750 if !ports.contains(&&expected_port.into()) {
751 Some(tokens)
752 } else {
753 None
754 }
755 })
756 .collect();
757 if !missing.is_empty() {
758 diagnostics.push(Diagnostic::spanned(
759 op_span,
760 Level::Error,
761 format!(
762 "`{}` missing expected {} port(s): `{}`.",
763 op_name,
764 input_output,
765 Itertools::intersperse(
766 missing.into_iter().map(|port| Cow::Owned(
767 port.to_token_stream().to_string()
768 )),
769 Cow::Borrowed("`, `")
770 )
771 .collect::<String>()
772 ),
773 ));
774 }
775 }
776
777 emit_port_error(
778 operator.span(),
779 &op_name,
780 op_constraints.ports_inn,
781 self.flat_graph
782 .node_predecessor_edges(node_id)
783 .map(|edge_id| self.flat_graph.edge_ports(edge_id).1),
784 "input",
785 &mut self.diagnostics,
786 );
787 emit_port_error(
788 operator.span(),
789 &op_name,
790 op_constraints.ports_out,
791 self.flat_graph
792 .node_successor_edges(node_id)
793 .map(|edge_id| self.flat_graph.edge_ports(edge_id).0),
794 "output",
795 &mut self.diagnostics,
796 );
797
798 {
800 let singletons_resolved =
801 self.flat_graph.node_singleton_references(node_id);
802 for (singleton_node_id, singleton_ident) in singletons_resolved
803 .iter()
804 .zip_eq(&*operator.singletons_referenced)
805 {
806 let &Some(singleton_node_id) = singleton_node_id else {
807 continue;
809 };
810 if !self.flat_graph.node_is_singleton(singleton_node_id) {
811 let op_name = self
812 .flat_graph
813 .node_op_inst(singleton_node_id)
814 .map(|inst| inst.op_constraints.name)
815 .unwrap_or("unknown");
816 self.diagnostics.push(Diagnostic::spanned(
817 singleton_ident.span(),
818 Level::Error,
819 format!(
820 "Cannot reference operator `{}`. Only singleton-producing operators can be referenced.",
821 op_name,
822 ),
823 ));
824 }
825 }
826 }
827 }
828 GraphNode::Handoff { .. } => todo!("Node::Handoff"),
829 GraphNode::ModuleBoundary { .. } => {
830 }
832 }
833 }
834 }
835
836 fn warn_unused_port_indexing(&mut self) {
839 for (_ident, varname_info) in self.varname_ends.iter() {
840 if !varname_info.inn_used {
841 Self::helper_check_unused_port(&mut self.diagnostics, &varname_info.ends, true);
842 }
843 if !varname_info.out_used {
844 Self::helper_check_unused_port(&mut self.diagnostics, &varname_info.ends, false);
845 }
846 }
847 }
848
849 fn helper_check_unused_port(diagnostics: &mut Diagnostics, ends: &Ends, is_in: bool) {
852 let port = if is_in { &ends.inn } else { &ends.out };
853 if let Some((port, _)) = port
854 && port.is_specified()
855 {
856 diagnostics.push(Diagnostic::spanned(
857 port.span(),
858 Level::Error,
859 format!(
860 "{} port index is unused. (Is the port on the correct side?)",
861 if is_in { "Input" } else { "Output" },
862 ),
863 ));
864 }
865 }
866
867 fn helper_combine_ends(
872 diagnostics: &mut Diagnostics,
873 og_ends: Ends,
874 inn_port: PortIndexValue,
875 out_port: PortIndexValue,
876 ) -> Ends {
877 Ends {
878 inn: Self::helper_combine_end(diagnostics, og_ends.inn, inn_port, "input"),
879 out: Self::helper_combine_end(diagnostics, og_ends.out, out_port, "output"),
880 }
881 }
882
883 fn helper_combine_end(
886 diagnostics: &mut Diagnostics,
887 og: Option<(PortIndexValue, GraphDet)>,
888 other: PortIndexValue,
889 input_output: &'static str,
890 ) -> Option<(PortIndexValue, GraphDet)> {
891 let other_span = other.span();
894
895 let (og_port, og_node) = og?;
896 match og_port.combine(other) {
897 Ok(combined_port) => Some((combined_port, og_node)),
898 Err(og_port) => {
899 diagnostics.push(Diagnostic::spanned(
901 og_port.span(),
902 Level::Error,
903 format!(
904 "Indexing on {} is overwritten below ({}) (1/2).",
905 input_output,
906 PrettySpan(other_span),
907 ),
908 ));
909 diagnostics.push(Diagnostic::spanned(
910 other_span,
911 Level::Error,
912 format!(
913 "Cannot index on already-indexed {}, previously indexed above ({}) (2/2).",
914 input_output,
915 PrettySpan(og_port.span()),
916 ),
917 ));
918 Some((og_port, og_node))
921 }
922 }
923 }
924
925 fn check_loop_errors(&mut self) {
927 for (node_id, node) in self.flat_graph.nodes() {
928 let Some(op_inst) = self.flat_graph.node_op_inst(node_id) else {
929 continue;
930 };
931 let loop_opt = self.flat_graph.node_loop(node_id);
932
933 for persistence in &op_inst.generics.persistence_args {
936 let span = op_inst.generics.generic_args.span();
937 match (loop_opt, persistence) {
938 (Some(_loop_id), p @ (Persistence::Tick | Persistence::Static)) => {
939 self.diagnostics.push(Diagnostic::spanned(
940 span,
941 Level::Error,
942 format!(
943 "Operator uses `'{}` persistence, which is not allowed within a `loop {{ ... }}` context.",
944 p.to_str_lowercase(),
945 ),
946 ));
947 }
948 (None, p @ (Persistence::None | Persistence::Loop)) => {
949 self.diagnostics.push(Diagnostic::spanned(
950 span,
951 Level::Error,
952 format!(
953 "Operator uses `'{}` persistence, but is not within a `loop {{ ... }}` context.",
954 p.to_str_lowercase(),
955 ),
956 ));
957 }
958 _ => {}
959 }
960 }
961
962 if let (Some(_loop_id), Some(FloType::Source)) =
964 (loop_opt, op_inst.op_constraints.flo_type)
965 {
966 self.diagnostics.push(Diagnostic::spanned(
967 node.span(),
968 Level::Error,
969 format!(
970 "Source operator `{}(...)` must be at the root level, not within any `loop {{ ... }}` contexts.",
971 op_inst.op_constraints.name
972 )
973 ));
974 }
975 }
976
977 for (_edge_id, (pred_id, node_id)) in self.flat_graph.edges() {
979 let Some(op_inst) = self.flat_graph.node_op_inst(node_id) else {
980 continue;
981 };
982 let flo_type = &op_inst.op_constraints.flo_type;
983
984 let pred_loop_id = self.flat_graph.node_loop(pred_id);
985 let loop_id = self.flat_graph.node_loop(node_id);
986
987 let span = self.flat_graph.node(node_id).span();
988
989 let (is_input, is_output) = {
990 let parent_pred_loop_id =
991 pred_loop_id.and_then(|lid| self.flat_graph.loop_parent(lid));
992 let parent_loop_id = loop_id.and_then(|lid| self.flat_graph.loop_parent(lid));
993 let is_same = pred_loop_id == loop_id;
994 let is_input = !is_same && parent_loop_id == pred_loop_id;
995 let is_output = !is_same && parent_pred_loop_id == loop_id;
996 if !(is_input || is_output || is_same) {
997 self.diagnostics.push(Diagnostic::spanned(
998 span,
999 Level::Error,
1000 "Operator input edge may not cross multiple loop contexts.",
1001 ));
1002 continue;
1003 }
1004 (is_input, is_output)
1005 };
1006
1007 match flo_type {
1008 None => {
1009 if is_input {
1010 self.diagnostics.push(Diagnostic::spanned(
1011 span,
1012 Level::Error,
1013 format!(
1014 "Operator `{}(...)` entering a loop context must be a windowing operator, but is not.",
1015 op_inst.op_constraints.name
1016 )
1017 ));
1018 }
1019 if is_output {
1020 self.diagnostics.push(Diagnostic::spanned(
1021 span,
1022 Level::Error,
1023 format!(
1024 "Operator `{}(...)` exiting a loop context must be an un-windowing operator, but is not.",
1025 op_inst.op_constraints.name
1026 )
1027 ));
1028 }
1029 }
1030 Some(FloType::Windowing) => {
1031 if !is_input {
1032 self.diagnostics.push(Diagnostic::spanned(
1033 span,
1034 Level::Error,
1035 format!(
1036 "Windowing operator `{}(...)` must be the first input operator into a `loop {{ ... }} context.",
1037 op_inst.op_constraints.name
1038 )
1039 ));
1040 }
1041 }
1042 Some(FloType::Unwindowing) => {
1043 if !is_output {
1044 self.diagnostics.push(Diagnostic::spanned(
1045 span,
1046 Level::Error,
1047 format!(
1048 "Un-windowing operator `{}(...)` must be the first output operator after exiting a `loop {{ ... }} context.",
1049 op_inst.op_constraints.name
1050 )
1051 ));
1052 }
1053 }
1054 Some(FloType::NextIteration) => {
1055 if loop_id.is_none() {
1057 self.diagnostics.push(Diagnostic::spanned(
1058 span,
1059 Level::Error,
1060 format!(
1061 "Operator `{}(...)` must be within a `loop {{ ... }}` context.",
1062 op_inst.op_constraints.name
1063 ),
1064 ));
1065 }
1066 }
1067 Some(FloType::Source) => {
1068 }
1070 }
1071 }
1072
1073 for (loop_id, loop_nodes) in self.flat_graph.loops() {
1077 let filter_next_iteration = |&node_id: &GraphNodeId| {
1079 self.flat_graph
1080 .node_op_inst(node_id)
1081 .map(|op_inst| Some(FloType::NextIteration) != op_inst.op_constraints.flo_type)
1082 .unwrap_or(true)
1083 };
1084
1085 let topo_sort_result = graph_algorithms::topo_sort(
1086 loop_nodes.iter().copied().filter(filter_next_iteration),
1087 |dst| {
1088 self.flat_graph
1089 .node_predecessor_nodes(dst)
1090 .filter(|&src| Some(loop_id) == self.flat_graph.node_loop(src))
1091 .filter(filter_next_iteration)
1092 },
1093 );
1094 if let Err(cycle) = topo_sort_result {
1095 let len = cycle.len();
1096 for (i, node_id) in cycle.into_iter().enumerate() {
1097 let span = self.flat_graph.node(node_id).span();
1098 self.diagnostics.push(Diagnostic::spanned(
1099 span,
1100 Level::Error,
1101 format!(
1102 "Operator forms an illegal cycle within a `loop {{ ... }}` block. Use `{}()` to pass data across loop iterations. ({}/{})",
1103 NEXT_ITERATION.name,
1104 i + 1,
1105 len,
1106 ),
1107 ));
1108 }
1109 }
1110 }
1111 }
1112}