1use std::collections::HashMap;
4use std::collections::hash_map::Entry;
5use std::iter;
6
7use itertools::Itertools;
8use petgraph::algo::dominators::{self, Dominators};
9use petgraph::visit::{Topo, Walker};
10use thiserror::Error;
11
12use crate::core::HugrNode;
13use crate::extension::SignatureError;
14
15use crate::ops::constant::ConstTypeError;
16use crate::ops::custom::{ExtensionOp, OpaqueOpError};
17use crate::ops::validate::{
18 ChildrenEdgeData, ChildrenValidationError, EdgeValidationError, OpValidityFlags,
19};
20use crate::ops::{NamedOp, OpName, OpTag, OpTrait, OpType, ValidateOp};
21use crate::types::EdgeKind;
22use crate::types::type_param::TypeParam;
23use crate::{Direction, Port, Visibility};
24
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 self.validate_linkage()?;
64 #[cfg(all(test, not(miri)))]
70 {
71 use crate::envelope::EnvelopeConfig;
72 use crate::hugr::hugrmut::HugrMut;
73 use crate::hugr::views::ExtractionResult;
74
75 let (mut hugr, node_map) = self.hugr.extract_hugr(self.hugr.module_root());
76 hugr.set_entrypoint(node_map.extracted_node(self.hugr.entrypoint()));
77 crate::envelope::test::check_hugr_roundtrip(&hugr, EnvelopeConfig::text());
80 }
81
82 Ok(())
83 }
84
85 fn validate_linkage(&self) -> Result<(), ValidationError<H::Node>> {
86 let mut node_sig_defn = HashMap::new();
91
92 for c in self.hugr.children(self.hugr.module_root()) {
93 let (func_name, sig, is_defn) = match self.hugr.get_optype(c) {
94 OpType::FuncDecl(fd) if fd.visibility() == &Visibility::Public => {
95 (fd.func_name(), fd.signature(), false)
96 }
97 OpType::FuncDefn(fd) if fd.visibility() == &Visibility::Public => {
98 (fd.func_name(), fd.signature(), true)
99 }
100 _ => continue,
101 };
102 match node_sig_defn.entry(func_name) {
103 Entry::Vacant(ve) => {
104 ve.insert((c, sig, is_defn));
105 }
106 Entry::Occupied(oe) => {
107 let (prev_c, prev_sig, prev_defn) = oe.get();
111 if prev_sig != &sig || is_defn || *prev_defn {
112 return Err(ValidationError::DuplicateExport {
113 link_name: func_name.clone(),
114 children: [*prev_c, c],
115 });
116 };
117 }
118 }
119 }
120 Ok(())
121 }
122
123 fn compute_dominator(
129 &self,
130 parent: H::Node,
131 ) -> (Dominators<portgraph::NodeIndex>, H::RegionPortgraphNodes) {
132 let (region, node_map) = self.hugr.region_portgraph(parent);
133 let entry_node = self.hugr.children(parent).next().unwrap();
134 let doms = dominators::simple_fast(®ion, node_map.to_portgraph(entry_node));
135 (doms, node_map)
136 }
137
138 fn validate_node(&self, node: H::Node) -> Result<(), ValidationError<H::Node>> {
144 let op_type = self.hugr.get_optype(node);
145
146 if let OpType::OpaqueOp(opaque) = op_type {
147 Err(OpaqueOpError::UnresolvedOp(
148 node,
149 opaque.unqualified_id().clone(),
150 opaque.extension().clone(),
151 ))?;
152 }
153 for dir in Direction::BOTH {
156 let num_ports = self.hugr.num_ports(node, dir);
158 if num_ports != op_type.port_count(dir) {
159 return Err(ValidationError::WrongNumberOfPorts {
160 node,
161 optype: Box::new(op_type.clone()),
162 actual: num_ports,
163 expected: op_type.port_count(dir),
164 dir,
165 });
166 }
167 }
168
169 if node != self.hugr.module_root() {
170 let Some(parent) = self.hugr.get_parent(node) else {
171 return Err(ValidationError::NoParent { node });
172 };
173
174 let parent_optype = self.hugr.get_optype(parent);
175 let allowed_children = parent_optype.validity_flags::<H::Node>().allowed_children;
176 if !allowed_children.is_superset(op_type.tag()) {
177 return Err(ValidationError::InvalidParentOp {
178 child: node,
179 child_optype: Box::new(op_type.clone()),
180 parent,
181 parent_optype: Box::new(parent_optype.clone()),
182 allowed_children,
183 });
184 }
185 }
186
187 if node == self.hugr.entrypoint() {
189 let validity_flags: OpValidityFlags = op_type.validity_flags();
190 if validity_flags.allowed_children == OpTag::None {
191 return Err(ValidationError::EntrypointNotContainer {
192 node,
193 optype: Box::new(op_type.clone()),
194 });
195 }
196 }
197
198 self.validate_children(node, op_type)?;
200
201 if let OpType::Const(c) = op_type {
204 c.validate()?;
205 }
206
207 Ok(())
208 }
209
210 fn validate_port(
214 &mut self,
215 node: H::Node,
216 port: Port,
217 op_type: &OpType,
218 var_decls: &[TypeParam],
219 ) -> Result<(), ValidationError<H::Node>> {
220 let port_kind = op_type.port_kind(port).unwrap();
221 let dir = port.direction();
222
223 let mut links = self.hugr.linked_ports(node, port).peekable();
224 let outgoing_is_unique = port_kind.is_linear() || port_kind == EdgeKind::ControlFlow;
226 let incoming_is_unique = port_kind.is_value() || port_kind.is_const();
228 let must_be_connected = match dir {
229 Direction::Incoming => {
232 port_kind != EdgeKind::StateOrder
233 && port_kind != EdgeKind::ControlFlow
234 && op_type.tag() != OpTag::Case
235 }
236 Direction::Outgoing => outgoing_is_unique,
237 };
238 if must_be_connected && links.peek().is_none() {
239 return Err(ValidationError::UnconnectedPort {
240 node,
241 port,
242 port_kind: Box::new(port_kind),
243 });
244 }
245
246 if dir == Direction::Incoming {
248 if incoming_is_unique && links.nth(1).is_some() {
249 return Err(ValidationError::TooManyConnections {
250 node,
251 port,
252 port_kind: Box::new(port_kind),
253 });
254 }
255 return Ok(());
256 }
257
258 self.validate_port_kind(&port_kind, var_decls)
259 .map_err(|cause| ValidationError::SignatureError {
260 node,
261 op: op_type.name(),
262 cause,
263 })?;
264
265 let mut link_cnt = 0;
266 for (other_node, other_offset) in links {
267 link_cnt += 1;
268 if outgoing_is_unique && link_cnt > 1 {
269 return Err(ValidationError::TooManyConnections {
270 node,
271 port,
272 port_kind: Box::new(port_kind),
273 });
274 }
275
276 let other_op = self.hugr.get_optype(other_node);
277 let Some(other_kind) = other_op.port_kind(other_offset) else {
278 panic!(
279 "The number of ports in {other_node} does not match the operation definition. This should have been caught by `validate_node`."
280 );
281 };
282 if other_kind != port_kind {
283 return Err(ValidationError::IncompatiblePorts {
284 from: node,
285 from_port: port,
286 from_kind: Box::new(port_kind),
287 to: other_node,
288 to_port: other_offset,
289 to_kind: Box::new(other_kind),
290 });
291 }
292
293 self.validate_edge(node, port, op_type, other_node, other_offset)?;
294 }
295
296 Ok(())
297 }
298
299 fn validate_port_kind(
300 &self,
301 port_kind: &EdgeKind,
302 var_decls: &[TypeParam],
303 ) -> Result<(), SignatureError> {
304 match &port_kind {
305 EdgeKind::Value(ty) => ty.validate(var_decls),
306 EdgeKind::Const(ty) => ty.validate(&[]),
309 EdgeKind::Function(pf) => pf.validate(),
310 _ => Ok(()),
311 }
312 }
313
314 fn validate_children(
318 &self,
319 node: H::Node,
320 op_type: &OpType,
321 ) -> Result<(), ValidationError<H::Node>> {
322 let flags = op_type.validity_flags();
323
324 if self.hugr.children(node).count() > 0 {
325 if flags.allowed_children.is_empty() {
326 return Err(ValidationError::NonContainerWithChildren {
327 node,
328 optype: Box::new(op_type.clone()),
329 });
330 }
331
332 let all_children = self.hugr.children(node);
333 let mut first_two_children = all_children.clone().take(2);
334 let first_child = self.hugr.get_optype(first_two_children.next().unwrap());
335 if !flags.allowed_first_child.is_superset(first_child.tag()) {
336 return Err(ValidationError::InvalidInitialChild {
337 parent: node,
338 parent_optype: Box::new(op_type.clone()),
339 optype: Box::new(first_child.clone()),
340 expected: flags.allowed_first_child,
341 position: "first",
342 });
343 }
344
345 if let Some(second_child) = first_two_children
346 .next()
347 .map(|child| self.hugr.get_optype(child))
348 {
349 if !flags.allowed_second_child.is_superset(second_child.tag()) {
350 return Err(ValidationError::InvalidInitialChild {
351 parent: node,
352 parent_optype: Box::new(op_type.clone()),
353 optype: Box::new(second_child.clone()),
354 expected: flags.allowed_second_child,
355 position: "second",
356 });
357 }
358 }
359 let children_optypes = all_children.map(|c| (c, self.hugr.get_optype(c)));
361 if let Err(source) = op_type.validate_op_children(children_optypes) {
362 return Err(ValidationError::InvalidChildren {
363 parent: node,
364 parent_optype: Box::new(op_type.clone()),
365 source,
366 });
367 }
368
369 if let Some(edge_check) = flags.edge_check {
371 for source in self.hugr.children(node) {
372 for target in self.hugr.output_neighbours(source) {
373 if self.hugr.get_parent(target) != Some(node) {
374 continue;
375 }
376 let source_op = self.hugr.get_optype(source);
377 let target_op = self.hugr.get_optype(target);
378 for [source_port, target_port] in self.hugr.node_connections(source, target)
379 {
380 let edge_data = ChildrenEdgeData {
381 source,
382 target,
383 source_port,
384 target_port,
385 source_op: source_op.clone(),
386 target_op: target_op.clone(),
387 };
388 if let Err(source) = edge_check(edge_data) {
389 return Err(ValidationError::InvalidEdges {
390 parent: node,
391 parent_optype: Box::new(op_type.clone()),
392 source,
393 });
394 }
395 }
396 }
397 }
398 }
399
400 if flags.requires_dag {
401 self.validate_children_dag(node, op_type)?;
402 }
403 } else if flags.requires_children {
404 return Err(ValidationError::ContainerWithoutChildren {
405 node,
406 optype: Box::new(op_type.clone()),
407 });
408 }
409
410 Ok(())
411 }
412
413 fn validate_children_dag(
418 &self,
419 parent: H::Node,
420 op_type: &OpType,
421 ) -> Result<(), ValidationError<H::Node>> {
422 if self.hugr.children(parent).next().is_none() {
423 return Ok(());
425 }
426
427 let (region, node_map) = self.hugr.region_portgraph(parent);
428 let postorder = Topo::new(®ion);
429 let nodes_visited = postorder
430 .iter(®ion)
431 .filter(|n| *n != node_map.to_portgraph(parent))
432 .count();
433 let node_count = self.hugr.children(parent).count();
434 if nodes_visited != node_count {
435 return Err(ValidationError::NotADag {
436 node: parent,
437 optype: Box::new(op_type.clone()),
438 });
439 }
440
441 Ok(())
442 }
443
444 fn validate_edge(
452 &mut self,
453 from: H::Node,
454 from_offset: Port,
455 from_optype: &OpType,
456 to: H::Node,
457 to_offset: Port,
458 ) -> Result<(), InterGraphEdgeError<H::Node>> {
459 let from_parent = self
460 .hugr
461 .get_parent(from)
462 .expect("Root nodes cannot have ports");
463 let to_parent = self.hugr.get_parent(to);
464 let edge_kind = from_optype.port_kind(from_offset).unwrap();
465 if Some(from_parent) == to_parent {
466 return Ok(()); }
468 let is_static = edge_kind.is_static();
469 if !is_static && !matches!(&edge_kind, EdgeKind::Value(t) if t.copyable()) {
470 return Err(InterGraphEdgeError::NonCopyableData {
471 from,
472 from_offset,
473 to,
474 to_offset,
475 ty: Box::new(edge_kind),
476 });
477 }
478
479 let from_parent_parent = self.hugr.get_parent(from_parent);
486 for (ancestor, ancestor_parent) in
487 iter::successors(to_parent, |&p| self.hugr.get_parent(p)).tuple_windows()
488 {
489 if ancestor_parent == from_parent {
490 if !is_static {
492 self.hugr
494 .node_connections(from, ancestor)
495 .find(|&[p, _]| from_optype.port_kind(p) == Some(EdgeKind::StateOrder))
496 .ok_or(InterGraphEdgeError::MissingOrderEdge {
497 from,
498 from_offset,
499 to,
500 to_offset,
501 to_ancestor: ancestor,
502 })?;
503 }
504 return Ok(());
505 } else if Some(ancestor_parent) == from_parent_parent && !is_static {
506 let ancestor_parent_op = self.hugr.get_optype(ancestor_parent);
508 if ancestor_parent_op.tag() != OpTag::Cfg {
509 return Err(InterGraphEdgeError::NonCFGAncestor {
510 from,
511 from_offset,
512 to,
513 to_offset,
514 ancestor_parent_op: Box::new(ancestor_parent_op.clone()),
515 });
516 }
517
518 let (dominator_tree, node_map) =
520 if let Some(tree) = self.dominators.get(&ancestor_parent) {
521 tree
522 } else {
523 let (tree, node_map) = self.compute_dominator(ancestor_parent);
524 self.dominators.insert(ancestor_parent, (tree, node_map));
525 self.dominators.get(&ancestor_parent).unwrap()
526 };
527 if !dominator_tree
528 .dominators(node_map.to_portgraph(ancestor))
529 .is_some_and(|mut ds| ds.any(|n| n == node_map.to_portgraph(from_parent)))
530 {
531 return Err(InterGraphEdgeError::NonDominatedAncestor {
532 from,
533 from_offset,
534 to,
535 to_offset,
536 from_parent,
537 ancestor,
538 });
539 }
540
541 return Ok(());
542 }
543 }
544
545 Err(InterGraphEdgeError::NoRelation {
546 from,
547 from_offset,
548 to,
549 to_offset,
550 })
551 }
552
553 fn validate_subtree(
556 &mut self,
557 node: H::Node,
558 var_decls: &[TypeParam],
559 ) -> Result<(), ValidationError<H::Node>> {
560 let op_type = self.hugr.get_optype(node);
561 let validate_ext = |ext_op: &ExtensionOp| -> Result<(), ValidationError<H::Node>> {
564 ext_op
566 .def()
567 .validate_args(ext_op.args(), var_decls)
568 .map_err(|cause| ValidationError::SignatureError {
569 node,
570 op: op_type.name(),
571 cause,
572 })
573 };
574 match op_type {
575 OpType::ExtensionOp(ext_op) => validate_ext(ext_op)?,
576 OpType::OpaqueOp(opaque) => {
577 Err(OpaqueOpError::UnresolvedOp(
578 node,
579 opaque.qualified_id(),
580 opaque.extension().clone(),
581 ))?;
582 }
583 OpType::Call(c) => {
584 c.validate()
585 .map_err(|cause| ValidationError::SignatureError {
586 node,
587 op: op_type.name(),
588 cause,
589 })?;
590 }
591 OpType::LoadFunction(c) => {
592 c.validate()
593 .map_err(|cause| ValidationError::SignatureError {
594 node,
595 op: op_type.name(),
596 cause,
597 })?;
598 }
599 _ => (),
600 }
601
602 if node != self.hugr.entrypoint() {
606 for dir in Direction::BOTH {
607 for port in self.hugr.node_ports(node, dir) {
608 self.validate_port(node, port, op_type, var_decls)?;
609 }
610 }
611 }
612
613 let var_decls = if let OpType::FuncDefn(fd) = op_type {
616 fd.signature().params()
617 } else {
618 var_decls
619 };
620
621 for child in self.hugr.children(node) {
622 self.validate_subtree(child, var_decls)?;
623 }
624
625 Ok(())
626 }
627}
628
629#[derive(Debug, Clone, PartialEq, Error)]
631#[allow(missing_docs)]
632#[non_exhaustive]
633pub enum ValidationError<N: HugrNode> {
634 #[error("The root node of the Hugr ({node}) is not a root in the hierarchy.")]
636 RootNotRoot { node: N },
637 #[error(
639 "{node} has an invalid number of ports. The operation {optype} cannot have {actual} {dir:?} ports. Expected {expected}."
640 )]
641 WrongNumberOfPorts {
642 node: N,
643 optype: Box<OpType>,
644 actual: usize,
645 expected: usize,
646 dir: Direction,
647 },
648 #[error("{node} has an unconnected port {port} of type {port_kind}.")]
650 UnconnectedPort {
651 node: N,
652 port: Port,
653 port_kind: Box<EdgeKind>,
654 },
655 #[error("{node} has a port {port} of type {port_kind} with more than one connection.")]
657 TooManyConnections {
658 node: N,
659 port: Port,
660 port_kind: Box<EdgeKind>,
661 },
662 #[error(
664 "Connected ports {from_port} in {from} and {to_port} in {to} have incompatible kinds. Cannot connect {from_kind} to {to_kind}."
665 )]
666 IncompatiblePorts {
667 from: N,
668 from_port: Port,
669 from_kind: Box<EdgeKind>,
670 to: N,
671 to_port: Port,
672 to_kind: Box<EdgeKind>,
673 },
674 #[error("{node} has no parent.")]
676 NoParent { node: N },
677 #[error("The operation {parent_optype} cannot contain a {child_optype} as a child. Allowed children: {}. In {child} with parent {parent}.", allowed_children.description())]
679 InvalidParentOp {
680 child: N,
681 child_optype: Box<OpType>,
682 parent: N,
683 parent_optype: Box<OpType>,
684 allowed_children: OpTag,
685 },
686 #[error(
688 "A {optype} operation cannot be the {position} child of a {parent_optype}. Expected {expected}. In parent {parent}"
689 )]
690 InvalidInitialChild {
691 parent: N,
692 parent_optype: Box<OpType>,
693 optype: Box<OpType>,
694 expected: OpTag,
695 position: &'static str,
696 },
697 #[error(
699 "An operation {parent_optype} contains invalid children: {source}. In parent {parent}, child {child}",
700 child=source.child(),
701 )]
702 InvalidChildren {
703 parent: N,
704 parent_optype: Box<OpType>,
705 source: ChildrenValidationError<N>,
706 },
707 #[error("FuncDefn/Decl {} is exported under same name {link_name} as earlier node {}", children[0], children[1])]
711 DuplicateExport {
712 link_name: String,
714 children: [N; 2],
716 },
717 #[error(
719 "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:?}",
720 from=source.edge().source,
721 from_port=source.edge().source_port,
722 to=source.edge().target,
723 to_port=source.edge().target_port,
724 )]
725 InvalidEdges {
726 parent: N,
727 parent_optype: Box<OpType>,
728 source: EdgeValidationError<N>,
729 },
730 #[error("{node} with optype {optype} is not a container, but has children.")]
732 NonContainerWithChildren { node: N, optype: Box<OpType> },
733 #[error("{node} with optype {optype} must have children, but has none.")]
735 ContainerWithoutChildren { node: N, optype: Box<OpType> },
736 #[error(
738 "The children of an operation {optype} must form a DAG. Loops are not allowed. In {node}."
739 )]
740 NotADag { node: N, optype: Box<OpType> },
741 #[error(transparent)]
743 InterGraphEdgeError(#[from] InterGraphEdgeError<N>),
744 #[error(
746 "{node} needs a concrete ExtensionSet - inference will provide this for Case/CFG/Conditional/DataflowBlock/DFG/TailLoop only"
747 )]
748 ExtensionsNotInferred { node: N },
749 #[error("Error in signature of operation {op} at {node}: {cause}")]
751 SignatureError {
752 node: N,
753 op: OpName,
754 #[source]
755 cause: SignatureError,
756 },
757 #[error(transparent)]
762 OpaqueOpError(#[from] OpaqueOpError<N>),
763 #[error(transparent)]
769 ConstTypeError(#[from] ConstTypeError),
770 #[error("The HUGR entrypoint ({node}) must be a region container, but '{}' does not accept children.", optype.name())]
772 EntrypointNotContainer { node: N, optype: Box<OpType> },
773}
774
775#[derive(Debug, Clone, PartialEq, Error)]
777#[allow(missing_docs)]
778#[non_exhaustive]
779pub enum InterGraphEdgeError<N: HugrNode> {
780 #[error(
782 "Inter-graph edges can only carry copyable data. In an inter-graph edge from {from} ({from_offset}) to {to} ({to_offset}) with type {ty}."
783 )]
784 NonCopyableData {
785 from: N,
786 from_offset: Port,
787 to: N,
788 to_offset: Port,
789 ty: Box<EdgeKind>,
790 },
791 #[error(
793 "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})."
794 )]
795 NonCFGAncestor {
796 from: N,
797 from_offset: Port,
798 to: N,
799 to_offset: Port,
800 ancestor_parent_op: Box<OpType>,
801 },
802 #[error(
804 "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})."
805 )]
806 MissingOrderEdge {
807 from: N,
808 from_offset: Port,
809 to: N,
810 to_offset: Port,
811 to_ancestor: N,
812 },
813 #[error(
815 "The ancestors of an inter-graph edge are not related. In an inter-graph edge from {from} ({from_offset}) to {to} ({to_offset})."
816 )]
817 NoRelation {
818 from: N,
819 from_offset: Port,
820 to: N,
821 to_offset: Port,
822 },
823 #[error(
825 " 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})."
826 )]
827 NonDominatedAncestor {
828 from: N,
829 from_offset: Port,
830 to: N,
831 to_offset: Port,
832 from_parent: N,
833 ancestor: N,
834 },
835}
836
837#[cfg(test)]
838pub(crate) mod test;