1pub 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#[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
268pub 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 #[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 #[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 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 if port.index() < port_count {
326 return signature.port_type(port).cloned().map(EdgeKind::Value);
327 }
328
329 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 self.other_port_kind(dir)
339 }
340
341 #[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 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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
439 #[must_use]
440 pub fn value_input_count(&self) -> usize {
441 self.value_port_count(Direction::Incoming)
442 }
443
444 #[inline]
446 #[must_use]
447 pub fn value_output_count(&self) -> usize {
448 self.value_port_count(Direction::Outgoing)
449 }
450
451 #[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 #[inline]
462 #[must_use]
463 pub fn input_count(&self) -> usize {
464 self.port_count(Direction::Incoming)
465 }
466
467 #[inline]
469 #[must_use]
470 pub fn output_count(&self) -> usize {
471 self.port_count(Direction::Outgoing)
472 }
473
474 #[inline]
476 #[must_use]
477 pub fn is_container(&self) -> bool {
478 self.validity_flags::<Node>().allowed_children != OpTag::None
479 }
480
481 pub fn cast<T: MakeExtensionOp>(&self) -> Option<T> {
485 self.as_extension_op().and_then(ExtensionOp::cast)
486 }
487
488 #[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 pub fn used_extensions(&self) -> Result<ExtensionRegistry, ExtensionCollectionError> {
503 let mut reg = collect_op_types_extensions(None, self)?;
505 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
514macro_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
528pub type OpName = SmolStr;
530
531pub type OpNameRef = str;
533
534#[enum_dispatch]
535pub(crate) trait NamedOp {
538 fn name(&self) -> OpName;
540}
541
542pub trait StaticTag {
547 const TAG: OpTag;
549}
550
551#[enum_dispatch]
552pub trait OpTrait: Sized + Clone {
554 fn description(&self) -> &str;
556
557 fn tag(&self) -> OpTag;
559
560 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 fn dataflow_signature(&self) -> Option<Cow<'_, Signature>> {
577 None
578 }
579
580 fn other_input(&self) -> Option<EdgeKind> {
586 None
587 }
588
589 fn other_output(&self) -> Option<EdgeKind> {
595 None
596 }
597
598 fn static_input(&self) -> Option<EdgeKind> {
604 None
605 }
606
607 fn static_output(&self) -> Option<EdgeKind> {
613 None
614 }
615
616 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 fn substitute(&self, _subst: &Substitution) -> Self {
630 self.clone()
631 }
632}
633
634#[enum_dispatch]
636pub trait OpParent {
637 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]
671pub trait ValidateOp {
673 #[inline]
675 fn validity_flags<N: HugrNode>(&self) -> validate::OpValidityFlags<N> {
676 Default::default()
677 }
678
679 #[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
689macro_rules! impl_validate_op {
691 ($i: ident) => {
692 impl $crate::ops::ValidateOp for $i {}
693 };
694}
695
696use impl_validate_op;