hugr_core/hugr/
validate.rs

1//! HUGR invariant checks.
2
3use 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
28/// Structure keeping track of pre-computed information used in the validation
29/// process.
30///
31/// TODO: Consider implementing updatable dominator trees and storing it in the
32/// Hugr to avoid recomputing it every time.
33pub(super) struct ValidationContext<'a, H: HugrView> {
34    hugr: &'a H,
35    /// Dominator tree for each CFG region, using the container node as index.
36    dominators: HashMap<H::Node, (Dominators<portgraph::NodeIndex>, H::RegionPortgraphNodes)>,
37}
38
39impl<'a, H: HugrView> ValidationContext<'a, H> {
40    /// Create a new validation context.
41    pub fn new(hugr: &'a H) -> Self {
42        let dominators = HashMap::new();
43        Self { hugr, dominators }
44    }
45
46    /// Check the validity of the HUGR.
47    pub fn validate(&mut self) -> Result<(), ValidationError<H::Node>> {
48        // Root node must be a root in the hierarchy.
49        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        // Node-specific checks
56        for node in self.hugr.nodes().map_into() {
57            self.validate_node(node)?;
58        }
59
60        // Hierarchy and children. No type variables declared outside the root.
61        self.validate_subtree(self.hugr.entrypoint(), &[])?;
62
63        // In tests we take the opportunity to verify that the hugr
64        // serialization round-trips. We verify the schema of the serialization
65        // format only when an environment variable is set. This allows
66        // a developer to modify the definition of serialized types locally
67        // without having to change the schema.
68        #[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    /// Compute the dominator tree for a CFG region, identified by its container
78    /// node.
79    ///
80    /// The results of this computation should be cached in `self.dominators`.
81    /// We don't do it here to avoid mutable borrows.
82    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(&region, node_map.to_portgraph(entry_node));
89        (doms, node_map)
90    }
91
92    /// Check the constraints on a single node.
93    ///
94    /// This includes:
95    /// - Matching the number of ports with the signature
96    /// - Dataflow ports are correct. See `validate_df_port`
97    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        // The Hugr can have only one root node.
108
109        for dir in Direction::BOTH {
110            // Check that we have the correct amount of ports and edges.
111            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        // Entrypoints must be region containers.
142        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        // Thirdly that the node has correct children
153        self.validate_children(node, op_type)?;
154
155        // OpType-specific checks.
156        // TODO Maybe we should delegate these checks to the OpTypes themselves.
157        if let OpType::Const(c) = op_type {
158            c.validate()?;
159        };
160
161        Ok(())
162    }
163
164    /// Check whether a port is valid.
165    /// - Input ports and output linear ports must be connected
166    /// - The linked port must have a compatible type.
167    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        // Linear dataflow values must be used, and control must have somewhere to flow.
179        let outgoing_is_linear = port_kind.is_linear() || port_kind == EdgeKind::ControlFlow;
180        let must_be_connected = match dir {
181            // Incoming ports must be connected, except for state order ports, branch case nodes,
182            // and CFG nodes.
183            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        // Avoid double checking connected port types.
199        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            // Static edges must *not* refer to type variables declared by enclosing FuncDefns
250            // as these are only types at runtime.
251            EdgeKind::Const(ty) => ty.validate(&[]),
252            EdgeKind::Function(pf) => pf.validate(),
253            _ => Ok(()),
254        }
255    }
256
257    /// Check operation-specific constraints.
258    ///
259    /// These are flags defined for each operation type as an [`OpValidityFlags`] object.
260    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            // Additional validations running over the full list of children optypes
303            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            // Additional validations running over the edges of the contained graph
313            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    /// Ensure that the children of a node form a directed acyclic graph.
357    ///
358    /// Inter-graph edges are ignored. Only internal dataflow, constant, or
359    /// state order edges are considered.
360    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            // No children, nothing to do
367            return Ok(());
368        };
369
370        let (region, node_map) = self.hugr.region_portgraph(parent);
371        let postorder = Topo::new(&region);
372        let nodes_visited = postorder
373            .iter(&region)
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    /// Check the edge is valid, i.e. the source/target nodes are at appropriate
388    /// positions in the hierarchy for some locality:
389    /// - Local edges, of any kind;
390    /// - External edges, for static and value edges only: from a node to a sibling's descendant.
391    ///   For Value edges, there must also be an order edge between the copy and the sibling.
392    /// - Dominator edges, for value edges only: from a node in a BasicBlock node to
393    ///   a descendant of a post-dominating sibling of the BasicBlock.
394    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(()); // Local edge
410        }
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        // To detect either external or dominator edges, we traverse the ancestors
423        // of the target until we find either `from_parent` (in the external
424        // case), or the parent of `from_parent` (in the dominator case).
425        //
426        // This search could be sped-up with a pre-computed LCA structure, but
427        // for valid Hugrs this search should be very short.
428        //
429        // For Value edges only, we record any FuncDefn we went through; if there is
430        // any such, then that is an error, but we report that only if the dom/ext
431        // relation was otherwise ok (an error about an edge "entering" some ancestor
432        // node could be misleading if the source isn't where it's expected)
433        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                // External edge.
449                err_entered_func.map_or(Ok(()), Err)?;
450                if !is_static {
451                    // Must have an order edge.
452                    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                // Dominator edge
466                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                // Check domination
478                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    /// Validates that TypeArgs are valid wrt the [ExtensionRegistry] and that nodes
513    /// only refer to type variables declared by the closest enclosing FuncDefn.
514    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        // The op_type must be defined only in terms of type variables defined outside the node
521
522        let validate_ext = |ext_op: &ExtensionOp| -> Result<(), ValidationError<H::Node>> {
523            // Check TypeArgs are valid, and if we can, fit the declared TypeParams
524            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        // Check port connections.
562        //
563        // Root nodes are ignored, as they cannot have connected edges.
564        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        // For FuncDefn's, only the type variables declared by the FuncDefn can be referred to by nodes
573        // inside the function. (The same would be true for FuncDecl's, but they have no child nodes.)
574        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/// Errors that can occur while validating a Hugr.
589#[derive(Debug, Clone, PartialEq, Error)]
590#[allow(missing_docs)]
591#[non_exhaustive]
592pub enum ValidationError<N: HugrNode> {
593    /// The root node of the Hugr is not a root in the hierarchy.
594    #[error("The root node of the Hugr ({node}) is not a root in the hierarchy.")]
595    RootNotRoot { node: N },
596    /// The node ports do not match the operation signature.
597    #[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    /// A dataflow port is not connected.
606    #[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    /// A linear port is connected to more than one thing.
613    #[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    /// Connected ports have different types, or non-unifiable types.
620    #[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    /// The non-root node has no parent.
630    #[error("{node} has no parent.")]
631    NoParent { node: N },
632    /// The parent node is not compatible with the child node.
633    #[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    /// Invalid first/second child.
642    #[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    /// The children list has invalid elements.
651    #[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    /// The children graph has invalid edges.
661    #[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    /// The node operation is not a container, but has children.
674    #[error("{node} with optype {optype} is not a container, but has children.")]
675    NonContainerWithChildren { node: N, optype: OpType },
676    /// The node must have children, but has none.
677    #[error("{node} with optype {optype} must have children, but has none.")]
678    ContainerWithoutChildren { node: N, optype: OpType },
679    /// The children of a node do not form a DAG.
680    #[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    /// There are invalid inter-graph edges.
685    #[error(transparent)]
686    InterGraphEdgeError(#[from] InterGraphEdgeError<N>),
687    /// There are errors in the extension deltas.
688    #[error(transparent)]
689    ExtensionError(#[from] ExtensionError),
690    /// A node claims to still be awaiting extension inference. Perhaps it is not acted upon by inference.
691    #[error("{node} needs a concrete ExtensionSet - inference will provide this for Case/CFG/Conditional/DataflowBlock/DFG/TailLoop only")]
692    ExtensionsNotInferred { node: N },
693    /// Error in a node signature
694    #[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 in a [ExtensionOp] serialized as an [Opaque].
702    ///
703    /// [ExtensionOp]: crate::ops::ExtensionOp
704    /// [Opaque]: crate::ops::OpaqueOp
705    #[error(transparent)]
706    OpaqueOpError(#[from] OpaqueOpError<N>),
707    /// A [Const] contained a [Value] of unexpected [Type].
708    ///
709    /// [Const]: crate::ops::Const
710    /// [Value]: crate::ops::Value
711    /// [Type]: crate::types::Type
712    #[error(transparent)]
713    ConstTypeError(#[from] ConstTypeError),
714    /// The HUGR entrypoint must be a region container.
715    #[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/// Errors related to the inter-graph edge validations.
720#[derive(Debug, Clone, PartialEq, Error)]
721#[allow(missing_docs)]
722#[non_exhaustive]
723pub enum InterGraphEdgeError<N: HugrNode> {
724    /// Inter-Graph edges can only carry copyable data.
725    #[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    /// Inter-Graph edges may not enter into FuncDefns unless they are static
734    #[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    /// The grandparent of a dominator inter-graph edge must be a CFG container.
743    #[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    /// The sibling ancestors of the external inter-graph edge endpoints must be have an order edge between them.
752    #[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    /// The ancestors of an inter-graph edge are not related.
761    #[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    /// The basic block containing the source node does not dominate the basic block containing the target node.
769    #[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;