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::{NamedOp, OpName, OpTag, OpTrait, OpType, ValidateOp};
20use crate::types::EdgeKind;
21use crate::types::type_param::TypeParam;
22use crate::{Direction, Port};
23
24use super::ExtensionError;
25use super::internal::PortgraphNodeMap;
26use super::views::HugrView;
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 use crate::envelope::EnvelopeConfig;
71 use crate::hugr::hugrmut::HugrMut;
72 use crate::hugr::views::ExtractionResult;
73
74 let (mut hugr, node_map) = self.hugr.extract_hugr(self.hugr.module_root());
75 hugr.set_entrypoint(node_map.extracted_node(self.hugr.entrypoint()));
76 crate::envelope::test::check_hugr_roundtrip(&hugr, EnvelopeConfig::text());
79 }
80
81 Ok(())
82 }
83
84 fn compute_dominator(
90 &self,
91 parent: H::Node,
92 ) -> (Dominators<portgraph::NodeIndex>, H::RegionPortgraphNodes) {
93 let (region, node_map) = self.hugr.region_portgraph(parent);
94 let entry_node = self.hugr.children(parent).next().unwrap();
95 let doms = dominators::simple_fast(®ion, node_map.to_portgraph(entry_node));
96 (doms, node_map)
97 }
98
99 fn validate_node(&self, node: H::Node) -> Result<(), ValidationError<H::Node>> {
105 let op_type = self.hugr.get_optype(node);
106
107 if let OpType::OpaqueOp(opaque) = op_type {
108 Err(OpaqueOpError::UnresolvedOp(
109 node,
110 opaque.unqualified_id().clone(),
111 opaque.extension().clone(),
112 ))?;
113 }
114 for dir in Direction::BOTH {
117 let num_ports = self.hugr.num_ports(node, dir);
119 if num_ports != op_type.port_count(dir) {
120 return Err(ValidationError::WrongNumberOfPorts {
121 node,
122 optype: op_type.clone(),
123 actual: num_ports,
124 expected: op_type.port_count(dir),
125 dir,
126 });
127 }
128 }
129
130 if node != self.hugr.module_root() {
131 let Some(parent) = self.hugr.get_parent(node) else {
132 return Err(ValidationError::NoParent { node });
133 };
134
135 let parent_optype = self.hugr.get_optype(parent);
136 let allowed_children = parent_optype.validity_flags::<H::Node>().allowed_children;
137 if !allowed_children.is_superset(op_type.tag()) {
138 return Err(ValidationError::InvalidParentOp {
139 child: node,
140 child_optype: op_type.clone(),
141 parent,
142 parent_optype: parent_optype.clone(),
143 allowed_children,
144 });
145 }
146 }
147
148 if node == self.hugr.entrypoint() {
150 let validity_flags: OpValidityFlags = op_type.validity_flags();
151 if validity_flags.allowed_children == OpTag::None {
152 return Err(ValidationError::EntrypointNotContainer {
153 node,
154 optype: op_type.clone(),
155 });
156 }
157 }
158
159 self.validate_children(node, op_type)?;
161
162 if let OpType::Const(c) = op_type {
165 c.validate()?;
166 }
167
168 Ok(())
169 }
170
171 fn validate_port(
175 &mut self,
176 node: H::Node,
177 port: Port,
178 op_type: &OpType,
179 var_decls: &[TypeParam],
180 ) -> Result<(), ValidationError<H::Node>> {
181 let port_kind = op_type.port_kind(port).unwrap();
182 let dir = port.direction();
183
184 let mut links = self.hugr.linked_ports(node, port).peekable();
185 let outgoing_is_linear = port_kind.is_linear() || port_kind == EdgeKind::ControlFlow;
187 let must_be_connected = match dir {
188 Direction::Incoming => {
191 port_kind != EdgeKind::StateOrder
192 && port_kind != EdgeKind::ControlFlow
193 && op_type.tag() != OpTag::Case
194 }
195 Direction::Outgoing => outgoing_is_linear,
196 };
197 if must_be_connected && links.peek().is_none() {
198 return Err(ValidationError::UnconnectedPort {
199 node,
200 port,
201 port_kind,
202 });
203 }
204
205 if dir == Direction::Incoming {
207 return Ok(());
208 }
209
210 self.validate_port_kind(&port_kind, var_decls)
211 .map_err(|cause| ValidationError::SignatureError {
212 node,
213 op: op_type.name(),
214 cause,
215 })?;
216
217 let mut link_cnt = 0;
218 for (other_node, other_offset) in links {
219 link_cnt += 1;
220 if outgoing_is_linear && link_cnt > 1 {
221 return Err(ValidationError::TooManyConnections {
222 node,
223 port,
224 port_kind,
225 });
226 }
227
228 let other_op = self.hugr.get_optype(other_node);
229 let Some(other_kind) = other_op.port_kind(other_offset) else {
230 panic!(
231 "The number of ports in {other_node} does not match the operation definition. This should have been caught by `validate_node`."
232 );
233 };
234 if other_kind != port_kind {
235 return Err(ValidationError::IncompatiblePorts {
236 from: node,
237 from_port: port,
238 from_kind: port_kind,
239 to: other_node,
240 to_port: other_offset,
241 to_kind: other_kind,
242 });
243 }
244
245 self.validate_edge(node, port, op_type, other_node, other_offset)?;
246 }
247
248 Ok(())
249 }
250
251 fn validate_port_kind(
252 &self,
253 port_kind: &EdgeKind,
254 var_decls: &[TypeParam],
255 ) -> Result<(), SignatureError> {
256 match &port_kind {
257 EdgeKind::Value(ty) => ty.validate(var_decls),
258 EdgeKind::Const(ty) => ty.validate(&[]),
261 EdgeKind::Function(pf) => pf.validate(),
262 _ => Ok(()),
263 }
264 }
265
266 fn validate_children(
270 &self,
271 node: H::Node,
272 op_type: &OpType,
273 ) -> Result<(), ValidationError<H::Node>> {
274 let flags = op_type.validity_flags();
275
276 if self.hugr.children(node).count() > 0 {
277 if flags.allowed_children.is_empty() {
278 return Err(ValidationError::NonContainerWithChildren {
279 node,
280 optype: op_type.clone(),
281 });
282 }
283
284 let all_children = self.hugr.children(node);
285 let mut first_two_children = all_children.clone().take(2);
286 let first_child = self.hugr.get_optype(first_two_children.next().unwrap());
287 if !flags.allowed_first_child.is_superset(first_child.tag()) {
288 return Err(ValidationError::InvalidInitialChild {
289 parent: node,
290 parent_optype: op_type.clone(),
291 optype: first_child.clone(),
292 expected: flags.allowed_first_child,
293 position: "first",
294 });
295 }
296
297 if let Some(second_child) = first_two_children
298 .next()
299 .map(|child| self.hugr.get_optype(child))
300 {
301 if !flags.allowed_second_child.is_superset(second_child.tag()) {
302 return Err(ValidationError::InvalidInitialChild {
303 parent: node,
304 parent_optype: op_type.clone(),
305 optype: second_child.clone(),
306 expected: flags.allowed_second_child,
307 position: "second",
308 });
309 }
310 }
311 let children_optypes = all_children.map(|c| (c, self.hugr.get_optype(c)));
313 if let Err(source) = op_type.validate_op_children(children_optypes) {
314 return Err(ValidationError::InvalidChildren {
315 parent: node,
316 parent_optype: op_type.clone(),
317 source,
318 });
319 }
320
321 if let Some(edge_check) = flags.edge_check {
323 for source in self.hugr.children(node) {
324 for target in self.hugr.output_neighbours(source) {
325 if self.hugr.get_parent(target) != Some(node) {
326 continue;
327 }
328 let source_op = self.hugr.get_optype(source);
329 let target_op = self.hugr.get_optype(target);
330 for [source_port, target_port] in self.hugr.node_connections(source, target)
331 {
332 let edge_data = ChildrenEdgeData {
333 source,
334 target,
335 source_port,
336 target_port,
337 source_op: source_op.clone(),
338 target_op: target_op.clone(),
339 };
340 if let Err(source) = edge_check(edge_data) {
341 return Err(ValidationError::InvalidEdges {
342 parent: node,
343 parent_optype: op_type.clone(),
344 source,
345 });
346 }
347 }
348 }
349 }
350 }
351
352 if flags.requires_dag {
353 self.validate_children_dag(node, op_type)?;
354 }
355 } else if flags.requires_children {
356 return Err(ValidationError::ContainerWithoutChildren {
357 node,
358 optype: op_type.clone(),
359 });
360 }
361
362 Ok(())
363 }
364
365 fn validate_children_dag(
370 &self,
371 parent: H::Node,
372 op_type: &OpType,
373 ) -> Result<(), ValidationError<H::Node>> {
374 if self.hugr.children(parent).next().is_none() {
375 return Ok(());
377 }
378
379 let (region, node_map) = self.hugr.region_portgraph(parent);
380 let postorder = Topo::new(®ion);
381 let nodes_visited = postorder
382 .iter(®ion)
383 .filter(|n| *n != node_map.to_portgraph(parent))
384 .count();
385 let node_count = self.hugr.children(parent).count();
386 if nodes_visited != node_count {
387 return Err(ValidationError::NotADag {
388 node: parent,
389 optype: op_type.clone(),
390 });
391 }
392
393 Ok(())
394 }
395
396 fn validate_edge(
404 &mut self,
405 from: H::Node,
406 from_offset: Port,
407 from_optype: &OpType,
408 to: H::Node,
409 to_offset: Port,
410 ) -> Result<(), InterGraphEdgeError<H::Node>> {
411 let from_parent = self
412 .hugr
413 .get_parent(from)
414 .expect("Root nodes cannot have ports");
415 let to_parent = self.hugr.get_parent(to);
416 let edge_kind = from_optype.port_kind(from_offset).unwrap();
417 if Some(from_parent) == to_parent {
418 return Ok(()); }
420 let is_static = edge_kind.is_static();
421 if !is_static && !matches!(&edge_kind, EdgeKind::Value(t) if t.copyable()) {
422 return Err(InterGraphEdgeError::NonCopyableData {
423 from,
424 from_offset,
425 to,
426 to_offset,
427 ty: edge_kind,
428 });
429 }
430
431 let mut err_entered_func = None;
443 let from_parent_parent = self.hugr.get_parent(from_parent);
444 for (ancestor, ancestor_parent) in
445 iter::successors(to_parent, |&p| self.hugr.get_parent(p)).tuple_windows()
446 {
447 if !is_static && self.hugr.get_optype(ancestor).is_func_defn() {
448 err_entered_func.get_or_insert(InterGraphEdgeError::ValueEdgeIntoFunc {
449 to,
450 to_offset,
451 from,
452 from_offset,
453 func: ancestor,
454 });
455 }
456 if ancestor_parent == from_parent {
457 err_entered_func.map_or(Ok(()), Err)?;
459 if !is_static {
460 self.hugr
462 .node_connections(from, ancestor)
463 .find(|&[p, _]| from_optype.port_kind(p) == Some(EdgeKind::StateOrder))
464 .ok_or(InterGraphEdgeError::MissingOrderEdge {
465 from,
466 from_offset,
467 to,
468 to_offset,
469 to_ancestor: ancestor,
470 })?;
471 }
472 return Ok(());
473 } else if Some(ancestor_parent) == from_parent_parent && !is_static {
474 let ancestor_parent_op = self.hugr.get_optype(ancestor_parent);
476 if ancestor_parent_op.tag() != OpTag::Cfg {
477 return Err(InterGraphEdgeError::NonCFGAncestor {
478 from,
479 from_offset,
480 to,
481 to_offset,
482 ancestor_parent_op: ancestor_parent_op.clone(),
483 });
484 }
485 err_entered_func.map_or(Ok(()), Err)?;
486 let (dominator_tree, node_map) =
488 if let Some(tree) = self.dominators.get(&ancestor_parent) {
489 tree
490 } else {
491 let (tree, node_map) = self.compute_dominator(ancestor_parent);
492 self.dominators.insert(ancestor_parent, (tree, node_map));
493 self.dominators.get(&ancestor_parent).unwrap()
494 };
495 if !dominator_tree
496 .dominators(node_map.to_portgraph(ancestor))
497 .is_some_and(|mut ds| ds.any(|n| n == node_map.to_portgraph(from_parent)))
498 {
499 return Err(InterGraphEdgeError::NonDominatedAncestor {
500 from,
501 from_offset,
502 to,
503 to_offset,
504 from_parent,
505 ancestor,
506 });
507 }
508
509 return Ok(());
510 }
511 }
512
513 Err(InterGraphEdgeError::NoRelation {
514 from,
515 from_offset,
516 to,
517 to_offset,
518 })
519 }
520
521 fn validate_subtree(
524 &mut self,
525 node: H::Node,
526 var_decls: &[TypeParam],
527 ) -> Result<(), ValidationError<H::Node>> {
528 let op_type = self.hugr.get_optype(node);
529 let validate_ext = |ext_op: &ExtensionOp| -> Result<(), ValidationError<H::Node>> {
532 ext_op
534 .def()
535 .validate_args(ext_op.args(), var_decls)
536 .map_err(|cause| ValidationError::SignatureError {
537 node,
538 op: op_type.name(),
539 cause,
540 })
541 };
542 match op_type {
543 OpType::ExtensionOp(ext_op) => validate_ext(ext_op)?,
544 OpType::OpaqueOp(opaque) => {
545 Err(OpaqueOpError::UnresolvedOp(
546 node,
547 opaque.qualified_id(),
548 opaque.extension().clone(),
549 ))?;
550 }
551 OpType::Call(c) => {
552 c.validate()
553 .map_err(|cause| ValidationError::SignatureError {
554 node,
555 op: op_type.name(),
556 cause,
557 })?;
558 }
559 OpType::LoadFunction(c) => {
560 c.validate()
561 .map_err(|cause| ValidationError::SignatureError {
562 node,
563 op: op_type.name(),
564 cause,
565 })?;
566 }
567 _ => (),
568 }
569
570 if node != self.hugr.entrypoint() {
574 for dir in Direction::BOTH {
575 for port in self.hugr.node_ports(node, dir) {
576 self.validate_port(node, port, op_type, var_decls)?;
577 }
578 }
579 }
580
581 let var_decls = if let OpType::FuncDefn(fd) = op_type {
584 fd.signature().params()
585 } else {
586 var_decls
587 };
588
589 for child in self.hugr.children(node) {
590 self.validate_subtree(child, var_decls)?;
591 }
592
593 Ok(())
594 }
595}
596
597#[derive(Debug, Clone, PartialEq, Error)]
599#[allow(missing_docs)]
600#[non_exhaustive]
601pub enum ValidationError<N: HugrNode> {
602 #[error("The root node of the Hugr ({node}) is not a root in the hierarchy.")]
604 RootNotRoot { node: N },
605 #[error(
607 "{node} has an invalid number of ports. The operation {optype} cannot have {actual} {dir:?} ports. Expected {expected}."
608 )]
609 WrongNumberOfPorts {
610 node: N,
611 optype: OpType,
612 actual: usize,
613 expected: usize,
614 dir: Direction,
615 },
616 #[error("{node} has an unconnected port {port} of type {port_kind}.")]
618 UnconnectedPort {
619 node: N,
620 port: Port,
621 port_kind: EdgeKind,
622 },
623 #[error("{node} has a port {port} of type {port_kind} with more than one connection.")]
625 TooManyConnections {
626 node: N,
627 port: Port,
628 port_kind: EdgeKind,
629 },
630 #[error(
632 "Connected ports {from_port} in {from} and {to_port} in {to} have incompatible kinds. Cannot connect {from_kind} to {to_kind}."
633 )]
634 IncompatiblePorts {
635 from: N,
636 from_port: Port,
637 from_kind: EdgeKind,
638 to: N,
639 to_port: Port,
640 to_kind: EdgeKind,
641 },
642 #[error("{node} has no parent.")]
644 NoParent { node: N },
645 #[error("The operation {parent_optype} cannot contain a {child_optype} as a child. Allowed children: {}. In {child} with parent {parent}.", allowed_children.description())]
647 InvalidParentOp {
648 child: N,
649 child_optype: OpType,
650 parent: N,
651 parent_optype: OpType,
652 allowed_children: OpTag,
653 },
654 #[error(
656 "A {optype} operation cannot be the {position} child of a {parent_optype}. Expected {expected}. In parent {parent}"
657 )]
658 InvalidInitialChild {
659 parent: N,
660 parent_optype: OpType,
661 optype: OpType,
662 expected: OpTag,
663 position: &'static str,
664 },
665 #[error(
667 "An operation {parent_optype} contains invalid children: {source}. In parent {parent}, child {child}",
668 child=source.child(),
669 )]
670 InvalidChildren {
671 parent: N,
672 parent_optype: OpType,
673 source: ChildrenValidationError<N>,
674 },
675 #[error(
677 "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:?}",
678 from=source.edge().source,
679 from_port=source.edge().source_port,
680 to=source.edge().target,
681 to_port=source.edge().target_port,
682 )]
683 InvalidEdges {
684 parent: N,
685 parent_optype: OpType,
686 source: EdgeValidationError<N>,
687 },
688 #[error("{node} with optype {optype} is not a container, but has children.")]
690 NonContainerWithChildren { node: N, optype: OpType },
691 #[error("{node} with optype {optype} must have children, but has none.")]
693 ContainerWithoutChildren { node: N, optype: OpType },
694 #[error(
696 "The children of an operation {optype} must form a DAG. Loops are not allowed. In {node}."
697 )]
698 NotADag { node: N, optype: OpType },
699 #[error(transparent)]
701 InterGraphEdgeError(#[from] InterGraphEdgeError<N>),
702 #[error(transparent)]
704 ExtensionError(#[from] ExtensionError),
705 #[error(
707 "{node} needs a concrete ExtensionSet - inference will provide this for Case/CFG/Conditional/DataflowBlock/DFG/TailLoop only"
708 )]
709 ExtensionsNotInferred { node: N },
710 #[error("Error in signature of operation {op} at {node}: {cause}")]
712 SignatureError {
713 node: N,
714 op: OpName,
715 #[source]
716 cause: SignatureError,
717 },
718 #[error(transparent)]
723 OpaqueOpError(#[from] OpaqueOpError<N>),
724 #[error(transparent)]
730 ConstTypeError(#[from] ConstTypeError),
731 #[error("The HUGR entrypoint ({node}) must be a region container, but '{}' does not accept children.", optype.name())]
733 EntrypointNotContainer { node: N, optype: OpType },
734}
735
736#[derive(Debug, Clone, PartialEq, Error)]
738#[allow(missing_docs)]
739#[non_exhaustive]
740pub enum InterGraphEdgeError<N: HugrNode> {
741 #[error(
743 "Inter-graph edges can only carry copyable data. In an inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}) with type {ty}."
744 )]
745 NonCopyableData {
746 from: N,
747 from_offset: Port,
748 to: N,
749 to_offset: Port,
750 ty: EdgeKind,
751 },
752 #[error(
754 "Inter-graph Value edges cannot enter into FuncDefns. Inter-graph edge from {from} ({from_offset}) to {to} ({to_offset} enters FuncDefn {func}"
755 )]
756 ValueEdgeIntoFunc {
757 from: N,
758 from_offset: Port,
759 to: N,
760 to_offset: Port,
761 func: N,
762 },
763 #[error(
765 "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})."
766 )]
767 NonCFGAncestor {
768 from: N,
769 from_offset: Port,
770 to: N,
771 to_offset: Port,
772 ancestor_parent_op: OpType,
773 },
774 #[error(
776 "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})."
777 )]
778 MissingOrderEdge {
779 from: N,
780 from_offset: Port,
781 to: N,
782 to_offset: Port,
783 to_ancestor: N,
784 },
785 #[error(
787 "The ancestors of an inter-graph edge are not related. In an inter-graph edge from {from} ({from_offset}) to {to} ({to_offset})."
788 )]
789 NoRelation {
790 from: N,
791 from_offset: Port,
792 to: N,
793 to_offset: Port,
794 },
795 #[error(
797 " 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})."
798 )]
799 NonDominatedAncestor {
800 from: N,
801 from_offset: Port,
802 to: N,
803 to_offset: Port,
804 from_parent: N,
805 ancestor: N,
806 },
807}
808
809#[cfg(test)]
810pub(crate) mod test;