hugr_core/
ops.rs

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