Skip to main content

hugr_core/
ops.rs

1//! The operation types for the HUGR.
2//!
3//! Operations represent the nodes in a HUGR graph. Each operation has a type
4//! ([`OpType`]), which determines its behavior, signature, and what kind of
5//! edges can connect to it. We may have different operations:
6//! - Unified representation: All operations are represented by the [`OpType`] enum
7//! - Dataflow operations: Operations like [`DFG`], [`Call`], and [`Input`]/[`Output`]
8//!   that process data
9//! - Control flow operations: Operations like [`CFG`], [`Conditional`], and [`TailLoop`]
10//!   for program control flow
11//! - Module-level operations: Operations like [`FuncDefn`], [`FuncDecl`], and [`Module`]
12//!   for program structure
13//! - Extension operations: Custom operations via [`ExtensionOp`]
14//!
15//! # Example
16//!
17//! ```
18//! use hugr::ops::{OpType, OpTrait, Input, Output, DFG, Call, FuncDefn, FuncDecl, LoadConstant};
19//! use hugr::ops::{Module, CFG, TailLoop, Conditional};
20//! use hugr::ops::dataflow::IOTrait;
21//! use hugr::types::{Signature, PolyFuncType};
22//! use hugr::extension::prelude::{bool_t, usize_t};
23//! use hugr::{Direction};
24//!
25//! // Create a dataflow graph operation
26//! let sig = Signature::new(vec![usize_t(), bool_t()], vec![bool_t()]);
27//! let dfg_op: OpType = DFG { signature: sig.clone() }.into();
28//!
29//! // Query operation properties
30//! assert_eq!(dfg_op.value_port_count(Direction::Incoming), 2);
31//! assert_eq!(dfg_op.value_port_count(Direction::Outgoing), 1);
32//! assert!(dfg_op.is_container()); // DFG can contain child nodes
33//!
34//! // Create input and output operations for the DFG's children
35//! let input_op: OpType = Input::new(sig.input().clone()).into();
36//! let output_op: OpType = Output::new(sig.output().clone()).into();
37//!
38//! // Check dataflow signatures
39//! if let Some(out_sig) = output_op.dataflow_signature() {
40//!     println!("Output takes {} inputs", out_sig.input_count());
41//! }
42//!
43//! // Create a function call operation
44//! let func_sig = PolyFuncType::from(Signature::new(vec![usize_t()], vec![bool_t()]));
45//! let call_op: OpType = Call::try_new(func_sig.clone(), &[]).unwrap().into();
46//!
47//! // Access the call signature
48//! let call_sig = call_op.dataflow_signature().unwrap();
49//! assert_eq!(call_sig.input_count(), 1);
50//! assert_eq!(call_sig.output_count(), 1);
51//!
52//! // Create a load constant operation
53//! let load_op: OpType = LoadConstant { datatype: bool_t() }.into();
54//! assert!(load_op.static_input_port().is_some()); // Has static input for constant
55//!
56//! // Create a control flow graph operation
57//! let cfg_sig = Signature::new(vec![usize_t()], vec![bool_t()]);
58//! let cfg_op: OpType = CFG { signature: cfg_sig }.into();
59//! assert!(cfg_op.is_container()); // CFG contains basic blocks
60//!
61//! // Create a tail loop operation
62//! let tail_loop = TailLoop {
63//!     just_inputs: vec![usize_t()].into(),
64//!     just_outputs: vec![bool_t()].into(),
65//!     rest: vec![usize_t()].into(),
66//! };
67//! let loop_op: OpType = tail_loop.into();
68//! assert!(loop_op.is_container()); // Contains loop body
69//!
70//! // Create a conditional operation
71//! let conditional = Conditional {
72//!     sum_rows: vec![[usize_t()].into(), [bool_t()].into()],
73//!     other_inputs: [usize_t()].into(),
74//!     outputs: [bool_t()].into(),
75//! };
76//! let cond_op: OpType = conditional.into();
77//! assert!(cond_op.is_container()); // Contains case branches
78//! let cond_sig = cond_op.dataflow_signature().unwrap();
79//! assert_eq!(cond_sig.input_count(), 2); // Sum + other inputs
80//!
81//! // Create a module operation (root of HUGR)
82//! let module_op: OpType = Module::new().into();
83//! assert!(module_op.is_container()); // Contains functions and declarations
84//!
85//! // Create a function definition
86//! let func_defn: OpType = FuncDefn::new("my_func", func_sig.clone()).into();
87//! assert!(func_defn.static_output_port().is_some()); // Has static output
88//! assert!(func_defn.is_container()); // Can contain the function body
89//!
90//! // Create a function declaration (forward declaration)
91//! let func_decl: OpType = FuncDecl::new("external_func", func_sig).into();
92//! assert!(func_decl.static_output_port().is_some()); // Also has static output
93//! assert!(!func_decl.is_container()); // Declarations have no body
94//!
95//! ```
96
97pub mod constant;
98pub mod controlflow;
99pub mod custom;
100pub mod dataflow;
101pub mod handle;
102pub mod module;
103pub mod sum;
104pub mod tag;
105pub mod validate;
106use crate::core::HugrNode;
107use crate::extension::resolution::{
108    ExtensionCollectionError, collect_op_extension, collect_op_types_extensions,
109};
110use std::borrow::Cow;
111use std::cmp::Ordering;
112
113use crate::extension::simple_op::MakeExtensionOp;
114use crate::extension::{ExtensionId, ExtensionRegistry};
115use crate::types::{EdgeKind, Signature, Substitution};
116use crate::{Direction, Node, OutgoingPort, Port};
117use crate::{IncomingPort, PortIndex};
118use handle::NodeHandle;
119use pastey::paste;
120
121use enum_dispatch::enum_dispatch;
122
123pub use constant::{Const, Value};
124pub use controlflow::{BasicBlock, CFG, Case, Conditional, DataflowBlock, ExitBlock, TailLoop};
125pub use custom::{ExtensionOp, OpaqueOp};
126pub use dataflow::{
127    Call, CallIndirect, DFG, DataflowOpTrait, DataflowParent, Input, LoadConstant, LoadFunction,
128    Output,
129};
130pub use module::{AliasDecl, AliasDefn, FuncDecl, FuncDefn, Module};
131use smol_str::SmolStr;
132pub use sum::Tag;
133pub use tag::OpTag;
134
135#[enum_dispatch(OpTrait, NamedOp, ValidateOp, OpParent)]
136#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize)]
137#[cfg_attr(test, derive(proptest_derive::Arbitrary))]
138/// The concrete operation types for a node in the HUGR.
139#[non_exhaustive]
140#[allow(missing_docs)]
141#[serde(tag = "op")]
142pub enum OpType {
143    Module,
144    FuncDefn,
145    FuncDecl,
146    AliasDecl,
147    AliasDefn,
148    Const,
149    Input,
150    Output,
151    Call,
152    CallIndirect,
153    LoadConstant,
154    LoadFunction,
155    DFG,
156    #[serde(skip_deserializing, rename = "Extension")]
157    ExtensionOp,
158    #[serde(rename = "Extension")]
159    OpaqueOp,
160    Tag,
161    DataflowBlock,
162    ExitBlock,
163    TailLoop,
164    CFG,
165    Conditional,
166    Case,
167}
168
169fn optype_id(optype: &OpType) -> usize {
170    match optype {
171        OpType::Module(_) => 0,
172        OpType::FuncDefn(_) => 1,
173        OpType::FuncDecl(_) => 2,
174        OpType::AliasDecl(_) => 3,
175        OpType::AliasDefn(_) => 4,
176        OpType::Const(_) => 5,
177        OpType::Input(_) => 6,
178        OpType::Output(_) => 7,
179        OpType::Call(_) => 8,
180        OpType::CallIndirect(_) => 9,
181        OpType::LoadConstant(_) => 10,
182        OpType::LoadFunction(_) => 11,
183        OpType::DFG(_) => 12,
184        OpType::ExtensionOp(_) => 13,
185        OpType::OpaqueOp(_) => 14,
186        OpType::Tag(_) => 15,
187        OpType::DataflowBlock(_) => 16,
188        OpType::ExitBlock(_) => 17,
189        OpType::TailLoop(_) => 18,
190        OpType::CFG(_) => 19,
191        OpType::Conditional(_) => 20,
192        OpType::Case(_) => 21,
193    }
194}
195
196impl PartialOrd for OpType {
197    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
198        let a = optype_id(self);
199        let b = optype_id(other);
200        if a < b {
201            Some(Ordering::Less)
202        } else if a > b {
203            Some(Ordering::Greater)
204        } else {
205            match format!("{:?}", self).cmp(&format!("{:?}", other)) {
206                Ordering::Less => Some(Ordering::Less),
207                Ordering::Greater => Some(Ordering::Greater),
208                Ordering::Equal => None,
209            }
210        }
211    }
212}
213
214macro_rules! impl_op_ref_try_into {
215    ($Op: tt, $sname:ident) => {
216        paste! {
217            impl OpType {
218                #[doc = "If is an instance of `" $Op "` return a reference to it."]
219                #[must_use] pub fn [<as_ $sname:snake>](&self) -> Option<&$Op> {
220                    TryInto::<&$Op>::try_into(self).ok()
221                }
222
223                #[doc = "Returns `true` if the operation is an instance of `" $Op "`."]
224                #[must_use] pub fn [<is_ $sname:snake>](&self) -> bool {
225                    self.[<as_ $sname:snake>]().is_some()
226                }
227            }
228
229            impl<'a> TryFrom<&'a OpType> for &'a $Op {
230                type Error = ();
231                fn try_from(optype: &'a OpType) -> Result<Self, Self::Error> {
232                    if let OpType::$Op(l) = optype {
233                        Ok(l)
234                    } else {
235                        Err(())
236                    }
237                }
238            }
239        }
240    };
241    ($Op:tt) => {
242        impl_op_ref_try_into!($Op, $Op);
243    };
244}
245
246impl_op_ref_try_into!(Module);
247impl_op_ref_try_into!(FuncDefn);
248impl_op_ref_try_into!(FuncDecl);
249impl_op_ref_try_into!(AliasDecl);
250impl_op_ref_try_into!(AliasDefn);
251impl_op_ref_try_into!(Const);
252impl_op_ref_try_into!(Input);
253impl_op_ref_try_into!(Output);
254impl_op_ref_try_into!(Call);
255impl_op_ref_try_into!(CallIndirect);
256impl_op_ref_try_into!(LoadConstant);
257impl_op_ref_try_into!(LoadFunction);
258impl_op_ref_try_into!(DFG, dfg);
259impl_op_ref_try_into!(ExtensionOp);
260impl_op_ref_try_into!(Tag);
261impl_op_ref_try_into!(DataflowBlock);
262impl_op_ref_try_into!(ExitBlock);
263impl_op_ref_try_into!(TailLoop);
264impl_op_ref_try_into!(CFG, cfg);
265impl_op_ref_try_into!(Conditional);
266impl_op_ref_try_into!(Case);
267
268/// The default `OpType` (as returned by [`Default::default`])
269pub const DEFAULT_OPTYPE: OpType = OpType::Module(Module::new());
270
271impl Default for OpType {
272    fn default() -> Self {
273        DEFAULT_OPTYPE
274    }
275}
276
277impl std::fmt::Display for OpType {
278    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
279        write!(f, "{}", self.name())
280    }
281}
282
283impl OpType {
284    /// The edge kind for the non-dataflow ports of the operation, not described
285    /// by the signature.
286    ///
287    /// If not None, a single extra port of that kind will be present on
288    /// the given direction after any dataflow or constant ports.
289    #[inline]
290    #[must_use]
291    pub fn other_port_kind(&self, dir: Direction) -> Option<EdgeKind> {
292        match dir {
293            Direction::Incoming => self.other_input(),
294            Direction::Outgoing => self.other_output(),
295        }
296    }
297
298    /// The edge kind for the static ports of the operation, not described by
299    /// the dataflow signature.
300    ///
301    /// If not None, an extra input port of that kind will be present on the
302    /// given direction after any dataflow ports and before any
303    /// [`OpType::other_port_kind`] ports.
304    #[inline]
305    #[must_use]
306    pub fn static_port_kind(&self, dir: Direction) -> Option<EdgeKind> {
307        match dir {
308            Direction::Incoming => self.static_input(),
309            Direction::Outgoing => self.static_output(),
310        }
311    }
312
313    /// Returns the edge kind for the given port.
314    ///
315    /// The result may be a value port, a static port, or a non-dataflow port.
316    /// See [`OpType::dataflow_signature`], [`OpType::static_port_kind`], and
317    /// [`OpType::other_port_kind`].
318    pub fn port_kind(&self, port: impl Into<Port>) -> Option<EdgeKind> {
319        let signature = self.dataflow_signature().unwrap_or_default();
320        let port: Port = port.into();
321        let dir = port.direction();
322        let port_count = signature.port_count(dir);
323
324        // Dataflow ports
325        if port.index() < port_count {
326            return signature.port_type(port).cloned().map(EdgeKind::Value);
327        }
328
329        // Constant port
330        let static_kind = self.static_port_kind(dir);
331        if port.index() == port_count
332            && let Some(kind) = static_kind
333        {
334            return Some(kind);
335        }
336
337        // Non-dataflow ports
338        self.other_port_kind(dir)
339    }
340
341    /// The non-dataflow port for the operation, not described by the signature.
342    /// See `[OpType::other_port_kind]`.
343    ///
344    /// Returns None if there is no such port, or if the operation defines multiple non-dataflow ports.
345    #[must_use]
346    pub fn other_port(&self, dir: Direction) -> Option<Port> {
347        let df_count = self.value_port_count(dir);
348        let non_df_count = self.non_df_port_count(dir);
349        // if there is a static input it comes before the non_df_ports
350        let static_input =
351            usize::from(dir == Direction::Incoming && OpTag::StaticInput.is_superset(self.tag()));
352        if self.other_port_kind(dir).is_some() && non_df_count >= 1 {
353            Some(Port::new(dir, df_count + static_input))
354        } else {
355            None
356        }
357    }
358
359    /// The non-dataflow input port for the operation, not described by the signature.
360    /// See `[OpType::other_port]`.
361    #[inline]
362    #[must_use]
363    pub fn other_input_port(&self) -> Option<IncomingPort> {
364        self.other_port(Direction::Incoming)
365            .map(|p| p.as_incoming().unwrap())
366    }
367
368    /// The non-dataflow output port for the operation, not described by the signature.
369    /// See `[OpType::other_port]`.
370    #[inline]
371    #[must_use]
372    pub fn other_output_port(&self) -> Option<OutgoingPort> {
373        self.other_port(Direction::Outgoing)
374            .map(|p| p.as_outgoing().unwrap())
375    }
376
377    /// If the op has a static port, the port of that input.
378    ///
379    /// See [`OpType::static_input_port`] and [`OpType::static_output_port`].
380    #[inline]
381    #[must_use]
382    pub fn static_port(&self, dir: Direction) -> Option<Port> {
383        self.static_port_kind(dir)?;
384        Some(Port::new(dir, self.value_port_count(dir)))
385    }
386
387    /// If the op has a static input ([`Call`], [`LoadConstant`], and [`LoadFunction`]), the port of
388    /// that input.
389    #[inline]
390    #[must_use]
391    pub fn static_input_port(&self) -> Option<IncomingPort> {
392        self.static_port(Direction::Incoming)
393            .map(|p| p.as_incoming().unwrap())
394    }
395
396    /// If the op has a static output ([`Const`], [`FuncDefn`], [`FuncDecl`]), the port of that output.
397    #[inline]
398    #[must_use]
399    pub fn static_output_port(&self) -> Option<OutgoingPort> {
400        self.static_port(Direction::Outgoing)
401            .map(|p| p.as_outgoing().unwrap())
402    }
403
404    /// Return the dataflow value ports for the given direction.
405    ///
406    /// See [`OpType::value_input_ports`] and [`OpType::value_output_ports`].
407    #[inline]
408    #[must_use]
409    pub fn value_ports(&self, dir: Direction) -> impl DoubleEndedIterator<Item = Port> {
410        (0..self.value_port_count(dir)).map(move |i| Port::new(dir, i))
411    }
412
413    /// Return the dataflow value input ports for the given direction.
414    #[inline]
415    #[must_use]
416    pub fn value_input_ports(&self) -> impl DoubleEndedIterator<Item = IncomingPort> {
417        self.value_ports(Direction::Incoming)
418            .map(|p| p.as_incoming().unwrap())
419    }
420
421    /// Return the dataflow value output ports for the given direction.
422    #[inline]
423    #[must_use]
424    pub fn value_output_ports(&self) -> impl DoubleEndedIterator<Item = OutgoingPort> {
425        self.value_ports(Direction::Outgoing)
426            .map(|p| p.as_outgoing().unwrap())
427    }
428
429    /// The number of Value ports in given direction.
430    #[inline]
431    #[must_use]
432    pub fn value_port_count(&self, dir: portgraph::Direction) -> usize {
433        self.dataflow_signature()
434            .map_or(0, |sig| sig.port_count(dir))
435    }
436
437    /// The number of Value input ports.
438    #[inline]
439    #[must_use]
440    pub fn value_input_count(&self) -> usize {
441        self.value_port_count(Direction::Incoming)
442    }
443
444    /// The number of Value output ports.
445    #[inline]
446    #[must_use]
447    pub fn value_output_count(&self) -> usize {
448        self.value_port_count(Direction::Outgoing)
449    }
450
451    /// Returns the number of ports for the given direction.
452    #[inline]
453    #[must_use]
454    pub fn port_count(&self, dir: Direction) -> usize {
455        let has_static_port = self.static_port_kind(dir).is_some();
456        let non_df_count = self.non_df_port_count(dir);
457        self.value_port_count(dir) + usize::from(has_static_port) + non_df_count
458    }
459
460    /// Returns the number of inputs ports for the operation.
461    #[inline]
462    #[must_use]
463    pub fn input_count(&self) -> usize {
464        self.port_count(Direction::Incoming)
465    }
466
467    /// Returns the number of outputs ports for the operation.
468    #[inline]
469    #[must_use]
470    pub fn output_count(&self) -> usize {
471        self.port_count(Direction::Outgoing)
472    }
473
474    /// Checks whether the operation can contain children nodes.
475    #[inline]
476    #[must_use]
477    pub fn is_container(&self) -> bool {
478        self.validity_flags::<Node>().allowed_children != OpTag::None
479    }
480
481    /// Cast to an extension operation.
482    ///
483    /// Returns `None` if the operation is not of the requested type.
484    pub fn cast<T: MakeExtensionOp>(&self) -> Option<T> {
485        self.as_extension_op().and_then(ExtensionOp::cast)
486    }
487
488    /// Returns the extension where the operation is defined, if any.
489    #[must_use]
490    pub fn extension_id(&self) -> Option<&ExtensionId> {
491        match self {
492            OpType::OpaqueOp(opaque) => Some(opaque.extension()),
493            OpType::ExtensionOp(e) => Some(e.def().extension_id()),
494            _ => None,
495        }
496    }
497
498    /// Returns a registry with all the extensions required by the operation.
499    ///
500    /// This includes the operation extension in [`OpType::extension_id`], and any
501    /// extension required by the operation's signature types.
502    pub fn used_extensions(&self) -> Result<ExtensionRegistry, ExtensionCollectionError> {
503        // Collect extensions on the types.
504        let mut reg = collect_op_types_extensions(None, self)?;
505        // And on the operation definition itself.
506        if let Some(ext) = collect_op_extension(None, self)? {
507            reg.register(ext);
508        }
509        reg.extend_with_dependencies()?;
510        Ok(reg)
511    }
512}
513
514/// Macro used by operations that want their
515/// name to be the same as their type name
516macro_rules! impl_op_name {
517    ($i: ident) => {
518        impl $crate::ops::NamedOp for $i {
519            fn name(&self) -> $crate::ops::OpName {
520                stringify!($i).into()
521            }
522        }
523    };
524}
525
526use impl_op_name;
527
528/// A unique identifier for a operation.
529pub type OpName = SmolStr;
530
531/// Slice of a [`OpName`] operation identifier.
532pub type OpNameRef = str;
533
534#[enum_dispatch]
535/// Trait for setting name of `OpType` variants.
536// Separate to OpTrait to allow simple definition via impl_op_name
537pub(crate) trait NamedOp {
538    /// The name of the operation.
539    fn name(&self) -> OpName;
540}
541
542/// Trait statically querying the tag of an operation.
543///
544/// This is implemented by all `OpType` variants, and always contains the dynamic
545/// tag returned by `OpType::tag(&self)`.
546pub trait StaticTag {
547    /// The name of the operation.
548    const TAG: OpTag;
549}
550
551#[enum_dispatch]
552/// Trait implemented by all `OpType` variants.
553pub trait OpTrait: Sized + Clone {
554    /// A human-readable description of the operation.
555    fn description(&self) -> &str;
556
557    /// Tag identifying the operation.
558    fn tag(&self) -> OpTag;
559
560    /// Tries to create a specific [`NodeHandle`] for a node with this operation
561    /// type.
562    ///
563    /// Fails if the operation's [`OpTrait::tag`] does not match the
564    /// [`NodeHandle::TAG`] of the requested handle.
565    fn try_node_handle<N, H>(&self, node: N) -> Option<H>
566    where
567        N: HugrNode,
568        H: NodeHandle<N> + From<N>,
569    {
570        H::TAG.is_superset(self.tag()).then(|| node.into())
571    }
572
573    /// The signature of the operation.
574    ///
575    /// Only dataflow operations have a signature, otherwise returns None.
576    fn dataflow_signature(&self) -> Option<Cow<'_, Signature>> {
577        None
578    }
579
580    /// The edge kind for the non-dataflow inputs of the operation,
581    /// not described by the signature.
582    ///
583    /// If not None, a single extra input port of that kind will be
584    /// present.
585    fn other_input(&self) -> Option<EdgeKind> {
586        None
587    }
588
589    /// The edge kind for the non-dataflow outputs of the operation, not
590    /// described by the signature.
591    ///
592    /// If not None, a single extra output port of that kind will be
593    /// present.
594    fn other_output(&self) -> Option<EdgeKind> {
595        None
596    }
597
598    /// The edge kind for a single constant input of the operation, not
599    /// described by the dataflow signature.
600    ///
601    /// If not None, an extra input port of that kind will be present after the
602    /// dataflow input ports and before any [`OpTrait::other_input`] ports.
603    fn static_input(&self) -> Option<EdgeKind> {
604        None
605    }
606
607    /// The edge kind for a single constant output of the operation, not
608    /// described by the dataflow signature.
609    ///
610    /// If not None, an extra output port of that kind will be present after the
611    /// dataflow input ports and before any [`OpTrait::other_output`] ports.
612    fn static_output(&self) -> Option<EdgeKind> {
613        None
614    }
615
616    /// Get the number of non-dataflow multiports.
617    fn non_df_port_count(&self, dir: Direction) -> usize {
618        usize::from(
619            match dir {
620                Direction::Incoming => self.other_input(),
621                Direction::Outgoing => self.other_output(),
622            }
623            .is_some(),
624        )
625    }
626
627    /// Apply a type-level substitution to this `OpType`, i.e. replace
628    /// [type variables](crate::types::TypeArg::new_var_use) with new types.
629    fn substitute(&self, _subst: &Substitution) -> Self {
630        self.clone()
631    }
632}
633
634/// Properties of child graphs of ops, if the op has children.
635#[enum_dispatch]
636pub trait OpParent {
637    /// The inner function type of the operation, if it has a child dataflow
638    /// sibling graph.
639    ///
640    /// Non-container ops like `FuncDecl` return `None` even though they represent a function.
641    fn inner_function_type(&self) -> Option<Cow<'_, Signature>> {
642        None
643    }
644}
645
646impl<T: DataflowParent> OpParent for T {
647    fn inner_function_type(&self) -> Option<Cow<'_, Signature>> {
648        Some(DataflowParent::inner_signature(self))
649    }
650}
651
652impl OpParent for Module {}
653impl OpParent for AliasDecl {}
654impl OpParent for AliasDefn {}
655impl OpParent for Const {}
656impl OpParent for Input {}
657impl OpParent for Output {}
658impl OpParent for Call {}
659impl OpParent for CallIndirect {}
660impl OpParent for LoadConstant {}
661impl OpParent for LoadFunction {}
662impl OpParent for ExtensionOp {}
663impl OpParent for OpaqueOp {}
664impl OpParent for Tag {}
665impl OpParent for CFG {}
666impl OpParent for Conditional {}
667impl OpParent for FuncDecl {}
668impl OpParent for ExitBlock {}
669
670#[enum_dispatch]
671/// Methods for Ops to validate themselves and children
672pub trait ValidateOp {
673    /// Returns a set of flags describing the validity predicates for this operation.
674    #[inline]
675    fn validity_flags<N: HugrNode>(&self) -> validate::OpValidityFlags<N> {
676        Default::default()
677    }
678
679    /// Validate the ordered list of children.
680    #[inline]
681    fn validate_op_children<'a, N: HugrNode>(
682        &self,
683        _children: impl DoubleEndedIterator<Item = (N, &'a OpType)>,
684    ) -> Result<(), validate::ChildrenValidationError<N>> {
685        Ok(())
686    }
687}
688
689/// Macro used for default implementation of `ValidateOp`
690macro_rules! impl_validate_op {
691    ($i: ident) => {
692        impl $crate::ops::ValidateOp for $i {}
693    };
694}
695
696use impl_validate_op;