1use std::collections::HashMap;
4use std::iter;
5
6use itertools::Itertools;
7use petgraph::algo::dominators::{self, Dominators};
8use petgraph::visit::{Topo, Walker};
9use thiserror::Error;
10
11use crate::core::HugrNode;
12use crate::extension::SignatureError;
13
14use crate::ops::constant::ConstTypeError;
15use crate::ops::custom::{ExtensionOp, OpaqueOpError};
16use crate::ops::validate::{
17 ChildrenEdgeData, ChildrenValidationError, EdgeValidationError, OpValidityFlags,
18};
19use crate::ops::{FuncDefn, NamedOp, OpName, OpTag, OpTrait, OpType, ValidateOp};
20use crate::types::type_param::TypeParam;
21use crate::types::EdgeKind;
22use crate::{Direction, Port};
23
24use super::internal::PortgraphNodeMap;
25use super::views::HugrView;
26use super::ExtensionError;
27
28pub(super) struct ValidationContext<'a, H: HugrView> {
34 hugr: &'a H,
35 dominators: HashMap<H::Node, (Dominators<portgraph::NodeIndex>, H::RegionPortgraphNodes)>,
37}
38
39impl<'a, H: HugrView> ValidationContext<'a, H> {
40 pub fn new(hugr: &'a H) -> Self {
42 let dominators = HashMap::new();
43 Self { hugr, dominators }
44 }
45
46 pub fn validate(&mut self) -> Result<(), ValidationError<H::Node>> {
48 if self.hugr.get_parent(self.hugr.module_root()).is_some() {
50 return Err(ValidationError::RootNotRoot {
51 node: self.hugr.module_root(),
52 });
53 }
54
55 for node in self.hugr.nodes().map_into() {
57 self.validate_node(node)?;
58 }
59
60 self.validate_subtree(self.hugr.entrypoint(), &[])?;
62
63 #[cfg(all(test, not(miri)))]
69 {
70 let test_schema = std::env::var("HUGR_TEST_SCHEMA").is_ok_and(|x| !x.is_empty());
71 crate::hugr::serialize::test::check_hugr_roundtrip(self.hugr, test_schema);
72 }
73
74 Ok(())
75 }
76
77 fn compute_dominator(
83 &self,
84 parent: H::Node,
85 ) -> (Dominators<portgraph::NodeIndex>, H::RegionPortgraphNodes) {
86 let (region, node_map) = self.hugr.region_portgraph(parent);
87 let entry_node = self.hugr.children(parent).next().unwrap();
88 let doms = dominators::simple_fast(®ion, node_map.to_portgraph(entry_node));
89 (doms, node_map)
90 }
91
92 fn validate_node(&self, node: H::Node) -> Result<(), ValidationError<H::Node>> {
98 let op_type = self.hugr.get_optype(node);
99
100 if let OpType::OpaqueOp(opaque) = op_type {
101 Err(OpaqueOpError::UnresolvedOp(
102 node,
103 opaque.unqualified_id().clone(),
104 opaque.extension().clone(),
105 ))?;
106 }
107 for dir in Direction::BOTH {
110 let num_ports = self.hugr.num_ports(node, dir);
112 if num_ports != op_type.port_count(dir) {
113 return Err(ValidationError::WrongNumberOfPorts {
114 node,
115 optype: op_type.clone(),
116 actual: num_ports,
117 expected: op_type.port_count(dir),
118 dir,
119 });
120 }
121 }
122
123 if node != self.hugr.module_root() {
124 let Some(parent) = self.hugr.get_parent(node) else {
125 return Err(ValidationError::NoParent { node });
126 };
127
128 let parent_optype = self.hugr.get_optype(parent);
129 let allowed_children = parent_optype.validity_flags::<H::Node>().allowed_children;
130 if !allowed_children.is_superset(op_type.tag()) {
131 return Err(ValidationError::InvalidParentOp {
132 child: node,
133 child_optype: op_type.clone(),
134 parent,
135 parent_optype: parent_optype.clone(),
136 allowed_children,
137 });
138 }
139 }
140
141 if node == self.hugr.entrypoint() {
143 let validity_flags: OpValidityFlags = op_type.validity_flags();
144 if validity_flags.allowed_children == OpTag::None {
145 return Err(ValidationError::EntrypointNotContainer {
146 node,
147 optype: op_type.clone(),
148 });
149 }
150 }
151
152 self.validate_children(node, op_type)?;
154
155 if let OpType::Const(c) = op_type {
158 c.validate()?;
159 };
160
161 Ok(())
162 }
163
164 fn validate_port(
168 &mut self,
169 node: H::Node,
170 port: Port,
171 op_type: &OpType,
172 var_decls: &[TypeParam],
173 ) -> Result<(), ValidationError<H::Node>> {
174 let port_kind = op_type.port_kind(port).unwrap();
175 let dir = port.direction();
176
177 let mut links = self.hugr.linked_ports(node, port).peekable();
178 let outgoing_is_linear = port_kind.is_linear() || port_kind == EdgeKind::ControlFlow;
180 let must_be_connected = match dir {
181 Direction::Incoming => {
184 port_kind != EdgeKind::StateOrder
185 && port_kind != EdgeKind::ControlFlow
186 && op_type.tag() != OpTag::Case
187 }
188 Direction::Outgoing => outgoing_is_linear,
189 };
190 if must_be_connected && links.peek().is_none() {
191 return Err(ValidationError::UnconnectedPort {
192 node,
193 port,
194 port_kind,
195 });
196 }
197
198 if dir == Direction::Incoming {
200 return Ok(());
201 }
202
203 self.validate_port_kind(&port_kind, var_decls)
204 .map_err(|cause| ValidationError::SignatureError {
205 node,
206 op: op_type.name(),
207 cause,
208 })?;
209
210 let mut link_cnt = 0;
211 for (other_node, other_offset) in links {
212 link_cnt += 1;
213 if outgoing_is_linear && link_cnt > 1 {
214 return Err(ValidationError::TooManyConnections {
215 node,
216 port,
217 port_kind,
218 });
219 }
220
221 let other_op = self.hugr.get_optype(other_node);
222 let Some(other_kind) = other_op.port_kind(other_offset) else {
223 panic!("The number of ports in {other_node} does not match the operation definition. This should have been caught by `validate_node`.");
224 };
225 if other_kind != port_kind {
226 return Err(ValidationError::IncompatiblePorts {
227 from: node,
228 from_port: port,
229 from_kind: port_kind,
230 to: other_node,
231 to_port: other_offset,
232 to_kind: other_kind,
233 });
234 }
235
236 self.validate_edge(node, port, op_type, other_node, other_offset)?;
237 }
238
239 Ok(())
240 }
241
242 fn validate_port_kind(
243 &self,
244 port_kind: &EdgeKind,
245 var_decls: &[TypeParam],
246 ) -> Result<(), SignatureError> {
247 match &port_kind {
248 EdgeKind::Value(ty) => ty.validate(var_decls),
249 EdgeKind::Const(ty) => ty.validate(&[]),
252 EdgeKind::Function(pf) => pf.validate(),
253 _ => Ok(()),
254 }
255 }
256
257 fn validate_children(
261 &self,
262 node: H::Node,
263 op_type: &OpType,
264 ) -> Result<(), ValidationError<H::Node>> {
265 let flags = op_type.validity_flags();
266
267 if self.hugr.children(node).count() > 0 {
268 if flags.allowed_children.is_empty() {
269 return Err(ValidationError::NonContainerWithChildren {
270 node,
271 optype: op_type.clone(),
272 });
273 }
274
275 let all_children = self.hugr.children(node);
276 let mut first_two_children = all_children.clone().take(2);
277 let first_child = self.hugr.get_optype(first_two_children.next().unwrap());
278 if !flags.allowed_first_child.is_superset(first_child.tag()) {
279 return Err(ValidationError::InvalidInitialChild {
280 parent: node,
281 parent_optype: op_type.clone(),
282 optype: first_child.clone(),
283 expected: flags.allowed_first_child,
284 position: "first",
285 });
286 }
287
288 if let Some(second_child) = first_two_children
289 .next()
290 .map(|child| self.hugr.get_optype(child))
291 {
292 if !flags.allowed_second_child.is_superset(second_child.tag()) {
293 return Err(ValidationError::InvalidInitialChild {
294 parent: node,
295 parent_optype: op_type.clone(),
296 optype: second_child.clone(),
297 expected: flags.allowed_second_child,
298 position: "second",
299 });
300 }
301 }
302 let children_optypes = all_children.map(|c| (c, self.hugr.get_optype(c)));
304 if let Err(source) = op_type.validate_op_children(children_optypes) {
305 return Err(ValidationError::InvalidChildren {
306 parent: node,
307 parent_optype: op_type.clone(),
308 source,
309 });
310 }
311
312 if let Some(edge_check) = flags.edge_check {
314 for source in self.hugr.children(node) {
315 for target in self.hugr.output_neighbours(source) {
316 if self.hugr.get_parent(target) != Some(node) {
317 continue;
318 }
319 let source_op = self.hugr.get_optype(source);
320 let target_op = self.hugr.get_optype(target);
321 for [source_port, target_port] in self.hugr.node_connections(source, target)
322 {
323 let edge_data = ChildrenEdgeData {
324 source,
325 target,
326 source_port,
327 target_port,
328 source_op: source_op.clone(),
329 target_op: target_op.clone(),
330 };
331 if let Err(source) = edge_check(edge_data) {
332 return Err(ValidationError::InvalidEdges {
333 parent: node,
334 parent_optype: op_type.clone(),
335 source,
336 });
337 }
338 }
339 }
340 }
341 }
342
343 if flags.requires_dag {
344 self.validate_children_dag(node, op_type)?;
345 }
346 } else if flags.requires_children {
347 return Err(ValidationError::ContainerWithoutChildren {
348 node,
349 optype: op_type.clone(),
350 });
351 }
352
353 Ok(())
354 }
355
356 fn validate_children_dag(
361 &self,
362 parent: H::Node,
363 op_type: &OpType,
364 ) -> Result<(), ValidationError<H::Node>> {
365 if self.hugr.children(parent).next().is_none() {
366 return Ok(());
368 };
369
370 let (region, node_map) = self.hugr.region_portgraph(parent);
371 let postorder = Topo::new(®ion);
372 let nodes_visited = postorder
373 .iter(®ion)
374 .filter(|n| *n != node_map.to_portgraph(parent))
375 .count();
376 let node_count = self.hugr.children(parent).count();
377 if nodes_visited != node_count {
378 return Err(ValidationError::NotADag {
379 node: parent,
380 optype: op_type.clone(),
381 });
382 }
383
384 Ok(())
385 }
386
387 fn validate_edge(
395 &mut self,
396 from: H::Node,
397 from_offset: Port,
398 from_optype: &OpType,
399 to: H::Node,
400 to_offset: Port,
401 ) -> Result<(), InterGraphEdgeError<H::Node>> {
402 let from_parent = self
403 .hugr
404 .get_parent(from)
405 .expect("Root nodes cannot have ports");
406 let to_parent = self.hugr.get_parent(to);
407 let edge_kind = from_optype.port_kind(from_offset).unwrap();
408 if Some(from_parent) == to_parent {
409 return Ok(()); }
411 let is_static = edge_kind.is_static();
412 if !is_static && !matches!(&edge_kind, EdgeKind::Value(t) if t.copyable()) {
413 return Err(InterGraphEdgeError::NonCopyableData {
414 from,
415 from_offset,
416 to,
417 to_offset,
418 ty: edge_kind,
419 });
420 };
421
422 let mut err_entered_func = None;
434 let from_parent_parent = self.hugr.get_parent(from_parent);
435 for (ancestor, ancestor_parent) in
436 iter::successors(to_parent, |&p| self.hugr.get_parent(p)).tuple_windows()
437 {
438 if !is_static && self.hugr.get_optype(ancestor).is_func_defn() {
439 err_entered_func.get_or_insert(InterGraphEdgeError::ValueEdgeIntoFunc {
440 to,
441 to_offset,
442 from,
443 from_offset,
444 func: ancestor,
445 });
446 }
447 if ancestor_parent == from_parent {
448 err_entered_func.map_or(Ok(()), Err)?;
450 if !is_static {
451 self.hugr
453 .node_connections(from, ancestor)
454 .find(|&[p, _]| from_optype.port_kind(p) == Some(EdgeKind::StateOrder))
455 .ok_or(InterGraphEdgeError::MissingOrderEdge {
456 from,
457 from_offset,
458 to,
459 to_offset,
460 to_ancestor: ancestor,
461 })?;
462 }
463 return Ok(());
464 } else if Some(ancestor_parent) == from_parent_parent && !is_static {
465 let ancestor_parent_op = self.hugr.get_optype(ancestor_parent);
467 if ancestor_parent_op.tag() != OpTag::Cfg {
468 return Err(InterGraphEdgeError::NonCFGAncestor {
469 from,
470 from_offset,
471 to,
472 to_offset,
473 ancestor_parent_op: ancestor_parent_op.clone(),
474 });
475 }
476 err_entered_func.map_or(Ok(()), Err)?;
477 let (dominator_tree, node_map) = match self.dominators.get(&ancestor_parent) {
479 Some(tree) => tree,
480 None => {
481 let (tree, node_map) = self.compute_dominator(ancestor_parent);
482 self.dominators.insert(ancestor_parent, (tree, node_map));
483 self.dominators.get(&ancestor_parent).unwrap()
484 }
485 };
486 if !dominator_tree
487 .dominators(node_map.to_portgraph(ancestor))
488 .is_some_and(|mut ds| ds.any(|n| n == node_map.to_portgraph(from_parent)))
489 {
490 return Err(InterGraphEdgeError::NonDominatedAncestor {
491 from,
492 from_offset,
493 to,
494 to_offset,
495 from_parent,
496 ancestor,
497 });
498 }
499
500 return Ok(());
501 }
502 }
503
504 Err(InterGraphEdgeError::NoRelation {
505 from,
506 from_offset,
507 to,
508 to_offset,
509 })
510 }
511
512 fn validate_subtree(
515 &mut self,
516 node: H::Node,
517 var_decls: &[TypeParam],
518 ) -> Result<(), ValidationError<H::Node>> {
519 let op_type = self.hugr.get_optype(node);
520 let validate_ext = |ext_op: &ExtensionOp| -> Result<(), ValidationError<H::Node>> {
523 ext_op
525 .def()
526 .validate_args(ext_op.args(), var_decls)
527 .map_err(|cause| ValidationError::SignatureError {
528 node,
529 op: op_type.name(),
530 cause,
531 })
532 };
533 match op_type {
534 OpType::ExtensionOp(ext_op) => validate_ext(ext_op)?,
535 OpType::OpaqueOp(opaque) => {
536 Err(OpaqueOpError::UnresolvedOp(
537 node,
538 opaque.qualified_id(),
539 opaque.extension().clone(),
540 ))?;
541 }
542 OpType::Call(c) => {
543 c.validate()
544 .map_err(|cause| ValidationError::SignatureError {
545 node,
546 op: op_type.name(),
547 cause,
548 })?;
549 }
550 OpType::LoadFunction(c) => {
551 c.validate()
552 .map_err(|cause| ValidationError::SignatureError {
553 node,
554 op: op_type.name(),
555 cause,
556 })?;
557 }
558 _ => (),
559 }
560
561 if node != self.hugr.entrypoint() {
565 for dir in Direction::BOTH {
566 for port in self.hugr.node_ports(node, dir) {
567 self.validate_port(node, port, op_type, var_decls)?;
568 }
569 }
570 }
571
572 let var_decls = if let OpType::FuncDefn(FuncDefn { signature, .. }) = op_type {
575 signature.params()
576 } else {
577 var_decls
578 };
579
580 for child in self.hugr.children(node) {
581 self.validate_subtree(child, var_decls)?;
582 }
583
584 Ok(())
585 }
586}
587
588#[derive(Debug, Clone, PartialEq, Error)]
590#[allow(missing_docs)]
591#[non_exhaustive]
592pub enum ValidationError<N: HugrNode> {
593 #[error("The root node of the Hugr ({node}) is not a root in the hierarchy.")]
595 RootNotRoot { node: N },
596 #[error("{node} has an invalid number of ports. The operation {optype} cannot have {actual} {dir:?} ports. Expected {expected}.")]
598 WrongNumberOfPorts {
599 node: N,
600 optype: OpType,
601 actual: usize,
602 expected: usize,
603 dir: Direction,
604 },
605 #[error("{node} has an unconnected port {port} of type {port_kind}.")]
607 UnconnectedPort {
608 node: N,
609 port: Port,
610 port_kind: EdgeKind,
611 },
612 #[error("{node} has a port {port} of type {port_kind} with more than one connection.")]
614 TooManyConnections {
615 node: N,
616 port: Port,
617 port_kind: EdgeKind,
618 },
619 #[error("Connected ports {from_port} in {from} and {to_port} in {to} have incompatible kinds. Cannot connect {from_kind} to {to_kind}.")]
621 IncompatiblePorts {
622 from: N,
623 from_port: Port,
624 from_kind: EdgeKind,
625 to: N,
626 to_port: Port,
627 to_kind: EdgeKind,
628 },
629 #[error("{node} has no parent.")]
631 NoParent { node: N },
632 #[error("The operation {parent_optype} cannot contain a {child_optype} as a child. Allowed children: {}. In {child} with parent {parent}.", allowed_children.description())]
634 InvalidParentOp {
635 child: N,
636 child_optype: OpType,
637 parent: N,
638 parent_optype: OpType,
639 allowed_children: OpTag,
640 },
641 #[error("A {optype} operation cannot be the {position} child of a {parent_optype}. Expected {expected}. In parent {parent}")]
643 InvalidInitialChild {
644 parent: N,
645 parent_optype: OpType,
646 optype: OpType,
647 expected: OpTag,
648 position: &'static str,
649 },
650 #[error(
652 "An operation {parent_optype} contains invalid children: {source}. In parent {parent}, child {child}",
653 child=source.child(),
654 )]
655 InvalidChildren {
656 parent: N,
657 parent_optype: OpType,
658 source: ChildrenValidationError<N>,
659 },
660 #[error(
662 "An operation {parent_optype} contains invalid edges between its children: {source}. In parent {parent}, edge from {from:?} port {from_port:?} to {to:?} port {to_port:?}",
663 from=source.edge().source,
664 from_port=source.edge().source_port,
665 to=source.edge().target,
666 to_port=source.edge().target_port,
667 )]
668 InvalidEdges {
669 parent: N,
670 parent_optype: OpType,
671 source: EdgeValidationError<N>,
672 },
673 #[error("{node} with optype {optype} is not a container, but has children.")]
675 NonContainerWithChildren { node: N, optype: OpType },
676 #[error("{node} with optype {optype} must have children, but has none.")]
678 ContainerWithoutChildren { node: N, optype: OpType },
679 #[error(
681 "The children of an operation {optype} must form a DAG. Loops are not allowed. In {node}."
682 )]
683 NotADag { node: N, optype: OpType },
684 #[error(transparent)]
686 InterGraphEdgeError(#[from] InterGraphEdgeError<N>),
687 #[error(transparent)]
689 ExtensionError(#[from] ExtensionError),
690 #[error("{node} needs a concrete ExtensionSet - inference will provide this for Case/CFG/Conditional/DataflowBlock/DFG/TailLoop only")]
692 ExtensionsNotInferred { node: N },
693 #[error("Error in signature of operation {op} at {node}: {cause}")]
695 SignatureError {
696 node: N,
697 op: OpName,
698 #[source]
699 cause: SignatureError,
700 },
701 #[error(transparent)]
706 OpaqueOpError(#[from] OpaqueOpError<N>),
707 #[error(transparent)]
713 ConstTypeError(#[from] ConstTypeError),
714 #[error("The HUGR entrypoint ({node}) must be a region container, but '{}' does not accept children.", optype.name())]
716 EntrypointNotContainer { node: N, optype: OpType },
717}
718
719#[derive(Debug, Clone, PartialEq, Error)]
721#[allow(missing_docs)]
722#[non_exhaustive]
723pub enum InterGraphEdgeError<N: HugrNode> {
724 #[error("Inter-graph edges can only carry copyable data. In an inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}) with type {ty}.")]
726 NonCopyableData {
727 from: N,
728 from_offset: Port,
729 to: N,
730 to_offset: Port,
731 ty: EdgeKind,
732 },
733 #[error("Inter-graph Value edges cannot enter into FuncDefns. Inter-graph edge from {from} ({from_offset}) to {to} ({to_offset} enters FuncDefn {func}")]
735 ValueEdgeIntoFunc {
736 from: N,
737 from_offset: Port,
738 to: N,
739 to_offset: Port,
740 func: N,
741 },
742 #[error("The grandparent of a dominator inter-graph edge must be a CFG container. Found operation {ancestor_parent_op}. In a dominator inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}).")]
744 NonCFGAncestor {
745 from: N,
746 from_offset: Port,
747 to: N,
748 to_offset: Port,
749 ancestor_parent_op: OpType,
750 },
751 #[error("Missing state order between the external inter-graph source {from} and the ancestor of the target {to_ancestor}. In an external inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}).")]
753 MissingOrderEdge {
754 from: N,
755 from_offset: Port,
756 to: N,
757 to_offset: Port,
758 to_ancestor: N,
759 },
760 #[error("The ancestors of an inter-graph edge are not related. In an inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}).")]
762 NoRelation {
763 from: N,
764 from_offset: Port,
765 to: N,
766 to_offset: Port,
767 },
768 #[error(" The basic block containing the source node does not dominate the basic block containing the target node in the CFG. Expected {from_parent} to dominate {ancestor}. In a dominator inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}).")]
770 NonDominatedAncestor {
771 from: N,
772 from_offset: Port,
773 to: N,
774 to_offset: Port,
775 from_parent: N,
776 ancestor: N,
777 },
778}
779
780#[cfg(test)]
781pub(crate) mod test;