diff --git a/hugr-py/src/hugr/serialization/ops.py b/hugr-py/src/hugr/serialization/ops.py index 9490a5c57..1517a85a7 100644 --- a/hugr-py/src/hugr/serialization/ops.py +++ b/hugr-py/src/hugr/serialization/ops.py @@ -300,13 +300,6 @@ class LoadConstant(DataflowOp): datatype: Type -class LeafOpBase(DataflowOp): - """Simple operation that has only value inputs+outputs and (potentially) StateOrder - edges.""" - - op: Literal["LeafOp"] = "LeafOp" - - class DFG(DataflowOp): """A simply nested dataflow graph.""" @@ -393,16 +386,11 @@ def insert_port_types(self, inputs: TypeRow, outputs: TypeRow) -> None: ControlFlowOp = Conditional | TailLoop | CFG -# ----------------------------------------- -# --------------- LeafOp ------------------ -# ----------------------------------------- - - -class CustomOp(LeafOpBase): +class CustomOp(DataflowOp): """A user-defined operation that can be downcasted by the extensions that define it.""" - lop: Literal["CustomOp"] = "CustomOp" + op: Literal["CustomOp"] = "CustomOp" extension: ExtensionId op_name: str signature: tys.FunctionType = Field(default_factory=tys.FunctionType.empty) @@ -425,10 +413,10 @@ class Config: } -class Noop(LeafOpBase): +class Noop(DataflowOp): """A no-op operation.""" - lop: Literal["Noop"] = "Noop" + op: Literal["Noop"] = "Noop" ty: Type def insert_port_types(self, in_types: TypeRow, out_types: TypeRow) -> None: @@ -438,10 +426,10 @@ def insert_port_types(self, in_types: TypeRow, out_types: TypeRow) -> None: self.ty = in_types[0] -class MakeTuple(LeafOpBase): +class MakeTuple(DataflowOp): """An operation that packs all its inputs into a tuple.""" - lop: Literal["MakeTuple"] = "MakeTuple" + op: Literal["MakeTuple"] = "MakeTuple" tys: TypeRow = Field(default_factory=list) def insert_port_types(self, in_types: TypeRow, out_types: TypeRow) -> None: @@ -451,56 +439,30 @@ def insert_port_types(self, in_types: TypeRow, out_types: TypeRow) -> None: self.tys = list(in_types) -class UnpackTuple(LeafOpBase): +class UnpackTuple(DataflowOp): """An operation that packs all its inputs into a tuple.""" - lop: Literal["UnpackTuple"] = "UnpackTuple" + op: Literal["UnpackTuple"] = "UnpackTuple" tys: TypeRow = Field(default_factory=list) def insert_port_types(self, in_types: TypeRow, out_types: TypeRow) -> None: self.tys = list(out_types) -class Tag(LeafOpBase): +class Tag(DataflowOp): """An operation that creates a tagged sum value from one of its variants.""" - lop: Literal["Tag"] = "Tag" + op: Literal["Tag"] = "Tag" tag: int # The variant to create. variants: TypeRow # The variants of the sum type. -class TypeApply(LeafOpBase): +class Lift(DataflowOp): """Fixes some TypeParams of a polymorphic type by providing TypeArgs.""" - lop: Literal["TypeApply"] = "TypeApply" - ta: "TypeApplication" - - -class TypeApplication(BaseModel): - """Records details of an application of a PolyFuncType to some TypeArgs and the - result (a less-, but still potentially-, polymorphic type). - """ - - input: PolyFuncType - args: list[tys.TypeTypeArg] - output: PolyFuncType - - class Config: - # Needed to avoid random '\n's in the pydantic description - json_schema_extra = { - "description": ( - "Records details of an application of a PolyFuncType to some TypeArgs " - "and the result (a less-, but still potentially-, polymorphic type)." - ) - } - - -class LeafOp(RootModel): - """A constant operation.""" - - root: CustomOp | Noop | MakeTuple | UnpackTuple | Tag | TypeApply = Field( - discriminator="lop" - ) + op: Literal["Lift"] = "Lift" + type_row: TypeRow + new_extension: ExtensionId class OpType(RootModel): @@ -522,7 +484,12 @@ class OpType(RootModel): | Call | CallIndirect | LoadConstant - | LeafOp + | CustomOp + | Noop + | MakeTuple + | UnpackTuple + | Tag + | Lift | DFG ) = Field(discriminator="op") diff --git a/hugr/src/algorithm/const_fold.rs b/hugr/src/algorithm/const_fold.rs index 6fcec022d..bbe82a48b 100644 --- a/hugr/src/algorithm/const_fold.rs +++ b/hugr/src/algorithm/const_fold.rs @@ -13,7 +13,7 @@ use crate::{ views::SiblingSubgraph, HugrMut, }, - ops::{Const, LeafOp}, + ops::{Const, OpType}, type_row, types::FunctionType, Hugr, HugrView, IncomingPort, Node, SimpleReplacement, @@ -44,13 +44,13 @@ pub(crate) fn sorted_consts(consts: &[(IncomingPort, Const)]) -> Vec<&Const> { .collect() } /// For a given op and consts, attempt to evaluate the op. -pub fn fold_leaf_op(op: &LeafOp, consts: &[(IncomingPort, Const)]) -> ConstFoldResult { +pub fn fold_leaf_op(op: &OpType, consts: &[(IncomingPort, Const)]) -> ConstFoldResult { match op { - LeafOp::Noop { .. } => out_row([consts.first()?.1.clone()]), - LeafOp::MakeTuple { .. } => { + OpType::Noop { .. } => out_row([consts.first()?.1.clone()]), + OpType::MakeTuple { .. } => { out_row([Const::tuple(sorted_consts(consts).into_iter().cloned())]) } - LeafOp::UnpackTuple { .. } => { + OpType::UnpackTuple { .. } => { let c = &consts.first()?.1; let Const::Tuple { vs } = c else { panic!("This op always takes a Tuple input."); @@ -58,15 +58,14 @@ pub fn fold_leaf_op(op: &LeafOp, consts: &[(IncomingPort, Const)]) -> ConstFoldR out_row(vs.iter().cloned()) } - LeafOp::Tag { tag, variants } => out_row([Const::sum( - *tag, + OpType::Tag(t) => out_row([Const::sum( + t.tag, consts.iter().map(|(_, konst)| konst.clone()), - SumType::new(variants.clone()), + SumType::new(t.variants.clone()), ) .unwrap()]), - LeafOp::CustomOp(_) => { - let ext_op = op.as_extension_op()?; - + OpType::CustomOp(op) => { + let ext_op = op.as_ref().as_extension_op()?; ext_op.constant_fold(consts) } _ => None, @@ -132,7 +131,7 @@ fn fold_op( reg: &ExtensionRegistry, ) -> Option<(SimpleReplacement, Vec)> { // only support leaf folding for now. - let neighbour_op = hugr.get_optype(op_node).as_leaf_op()?; + let neighbour_op = hugr.get_optype(op_node); let (in_consts, removals): (Vec<_>, Vec<_>) = hugr .node_inputs(op_node) .filter_map(|in_p| { @@ -214,7 +213,7 @@ mod test { use super::*; use crate::extension::prelude::{sum_with_error, BOOL_T}; use crate::extension::{ExtensionRegistry, PRELUDE}; - use crate::ops::OpType; + use crate::ops::{OpType, UnpackTuple}; use crate::std_extensions::arithmetic; use crate::std_extensions::arithmetic::conversions::ConvertOpDef; use crate::std_extensions::arithmetic::float_ops::FloatOps; @@ -242,7 +241,7 @@ mod test { fn test_add(#[case] a: f64, #[case] b: f64, #[case] c: f64) { let consts = vec![(0.into(), f2c(a)), (1.into(), f2c(b))]; let add_op: OpType = FloatOps::fadd.into(); - let out = fold_leaf_op(add_op.as_leaf_op().unwrap(), &consts).unwrap(); + let out = fold_leaf_op(&add_op, &consts).unwrap(); assert_eq!(&out[..], &[(0.into(), f2c(c))]); } @@ -264,7 +263,7 @@ mod test { let unpack = build .add_dataflow_op( - LeafOp::UnpackTuple { + UnpackTuple { tys: type_row![FLOAT64_TYPE, FLOAT64_TYPE], }, [tup], diff --git a/hugr/src/builder/build_traits.rs b/hugr/src/builder/build_traits.rs index 7b510df68..62a5a3b33 100644 --- a/hugr/src/builder/build_traits.rs +++ b/hugr/src/builder/build_traits.rs @@ -1,7 +1,7 @@ use crate::hugr::hugrmut::InsertionResult; use crate::hugr::views::HugrView; use crate::hugr::{NodeMetadata, ValidationError}; -use crate::ops::{self, LeafOp, OpTag, OpTrait, OpType}; +use crate::ops::{self, MakeTuple, OpTag, OpTrait, OpType, Tag}; use crate::utils::collect_array; use crate::{IncomingPort, Node, OutgoingPort}; @@ -471,13 +471,13 @@ pub trait Dataflow: Container { } } - /// Add a [`LeafOp::MakeTuple`] node and wire in the `values` Wires, + /// Add a [`MakeTuple`] node and wire in the `values` Wires, /// returning the Wire corresponding to the tuple. /// /// # Errors /// /// This function will return an error if there is an error adding the - /// [`LeafOp::MakeTuple`] node. + /// [`MakeTuple`] node. fn make_tuple(&mut self, values: impl IntoIterator) -> Result { let values = values.into_iter().collect_vec(); let types: Result, _> = values @@ -485,11 +485,11 @@ pub trait Dataflow: Container { .map(|&wire| self.get_wire_type(wire)) .collect(); let types = types?.into(); - let make_op = self.add_dataflow_op(LeafOp::MakeTuple { tys: types }, values)?; + let make_op = self.add_dataflow_op(MakeTuple { tys: types }, values)?; Ok(make_op.out_wire(0)) } - /// Add a [`LeafOp::Tag`] node and wire in the `value` Wire, + /// Add a [`Tag`] node and wire in the `value` Wire, /// to make a value with Sum type, with `tag` and possible types described /// by `variants`. /// Returns the Wire corresponding to the Sum value. @@ -505,7 +505,7 @@ pub trait Dataflow: Container { values: impl IntoIterator, ) -> Result { let make_op = self.add_dataflow_op( - LeafOp::Tag { + Tag { tag, variants: variants.into_iter().map(Into::into).collect_vec(), }, diff --git a/hugr/src/builder/circuit.rs b/hugr/src/builder/circuit.rs index 89d6b45a6..40018c096 100644 --- a/hugr/src/builder/circuit.rs +++ b/hugr/src/builder/circuit.rs @@ -244,7 +244,7 @@ mod test { Dataflow, DataflowSubContainer, Wire, }, extension::prelude::BOOL_T, - ops::{custom::OpaqueOp, LeafOp}, + ops::{custom::OpaqueOp, CustomOp}, type_row, types::FunctionType, }; @@ -278,16 +278,13 @@ mod test { #[test] fn with_nonlinear_and_outputs() { - let my_custom_op = LeafOp::CustomOp( - crate::ops::custom::ExternalOp::Opaque(OpaqueOp::new( - "MissingRsrc".try_into().unwrap(), - "MyOp", - "unknown op".to_string(), - vec![], - FunctionType::new(vec![QB, NAT], vec![QB]), - )) - .into(), - ); + let my_custom_op = CustomOp::new(crate::ops::custom::ExternalOp::Opaque(OpaqueOp::new( + "MissingRsrc".try_into().unwrap(), + "MyOp", + "unknown op".to_string(), + vec![], + FunctionType::new(vec![QB, NAT], vec![QB]), + ))); let build_res = build_main( FunctionType::new(type_row![QB, QB, NAT], type_row![QB, QB, BOOL_T]).into(), |mut f_build| { diff --git a/hugr/src/builder/dataflow.rs b/hugr/src/builder/dataflow.rs index cebdbd242..38bd4ec70 100644 --- a/hugr/src/builder/dataflow.rs +++ b/hugr/src/builder/dataflow.rs @@ -211,7 +211,7 @@ pub(crate) mod test { use crate::extension::prelude::BOOL_T; use crate::extension::{ExtensionId, EMPTY_REG}; use crate::hugr::validate::InterGraphEdgeError; - use crate::ops::{handle::NodeHandle, LeafOp, OpTag}; + use crate::ops::{handle::NodeHandle, Lift, Noop, OpTag}; use crate::std_extensions::logic::test::and_op; use crate::types::Type; @@ -347,13 +347,13 @@ pub(crate) mod test { )?; let [i1] = f_build.input_wires_arr(); - let noop = f_build.add_dataflow_op(LeafOp::Noop { ty: BIT }, [i1])?; + let noop = f_build.add_dataflow_op(Noop { ty: BIT }, [i1])?; let i1 = noop.out_wire(0); let mut nested = f_build.dfg_builder(FunctionType::new(type_row![], type_row![BIT]), None, [])?; - let id = nested.add_dataflow_op(LeafOp::Noop { ty: BIT }, [i1])?; + let id = nested.add_dataflow_op(Noop { ty: BIT }, [i1])?; let nested = nested.finish_with_outputs([id.out_wire(0)])?; @@ -371,13 +371,13 @@ pub(crate) mod test { )?; let [i1] = f_build.input_wires_arr(); - let noop = f_build.add_dataflow_op(LeafOp::Noop { ty: QB }, [i1])?; + let noop = f_build.add_dataflow_op(Noop { ty: QB }, [i1])?; let i1 = noop.out_wire(0); let mut nested = f_build.dfg_builder(FunctionType::new(type_row![], type_row![QB]), None, [])?; - let id_res = nested.add_dataflow_op(LeafOp::Noop { ty: QB }, [i1]); + let id_res = nested.add_dataflow_op(Noop { ty: QB }, [i1]); // The error would anyway be caught in validation when we finish the Hugr, // but the builder catches it earlier @@ -457,7 +457,7 @@ pub(crate) mod test { let [w] = add_ab.input_wires_arr(); let lift_a = add_ab.add_dataflow_op( - LeafOp::Lift { + Lift { type_row: type_row![BIT], new_extension: xa.clone(), }, @@ -467,7 +467,7 @@ pub(crate) mod test { let lift_b = add_ab.add_dataflow_node( NodeType::new( - LeafOp::Lift { + Lift { type_row: type_row![BIT], new_extension: xb, }, @@ -486,7 +486,7 @@ pub(crate) mod test { let [w] = add_c.input_wires_arr(); let lift_c = add_c.add_dataflow_node( NodeType::new( - LeafOp::Lift { + Lift { type_row: type_row![BIT], new_extension: xc, }, diff --git a/hugr/src/builder/tail_loop.rs b/hugr/src/builder/tail_loop.rs index 3d3022253..7e3fe099f 100644 --- a/hugr/src/builder/tail_loop.rs +++ b/hugr/src/builder/tail_loop.rs @@ -133,7 +133,7 @@ mod test { let _fdef = { let [b1] = fbuild .add_dataflow_op( - ops::LeafOp::Lift { + ops::Lift { type_row: type_row![BIT], new_extension: PRELUDE_ID, }, @@ -147,7 +147,7 @@ mod test { let const_val = Const::true_val(); let const_wire = loop_b.add_load_const(Const::true_val()); let lift_node = loop_b.add_dataflow_op( - ops::LeafOp::Lift { + ops::Lift { type_row: vec![const_val.const_type().clone()].into(), new_extension: PRELUDE_ID, }, diff --git a/hugr/src/extension/infer/test.rs b/hugr/src/extension/infer/test.rs index 256480614..cb03d6d94 100644 --- a/hugr/src/extension/infer/test.rs +++ b/hugr/src/extension/infer/test.rs @@ -11,7 +11,7 @@ use crate::hugr::{Hugr, HugrMut, HugrView, NodeType}; use crate::macros::const_extension_ids; use crate::ops::custom::{ExternalOp, OpaqueOp}; use crate::ops::{self, dataflow::IOTrait}; -use crate::ops::{LeafOp, OpType}; +use crate::ops::{CustomOp, Lift, OpType}; #[cfg(feature = "extension_inference")] use crate::{ builder::test::closed_dfg_root_hugr, @@ -316,7 +316,7 @@ fn test_conditional_inference() -> Result<(), Box> { let lift1 = hugr.add_node_with_parent( case, - ops::LeafOp::Lift { + Lift { type_row: type_row![NAT], new_extension: first_ext, }, @@ -324,7 +324,7 @@ fn test_conditional_inference() -> Result<(), Box> { let lift2 = hugr.add_node_with_parent( case, - ops::LeafOp::Lift { + Lift { type_row: type_row![NAT], new_extension: second_ext, }, @@ -416,7 +416,7 @@ fn extension_adding_sequence() -> Result<(), Box> { let lift = hugr.add_node_with_parent( node, - ops::LeafOp::Lift { + Lift { type_row: type_row![NAT], new_extension: ext, }, @@ -438,7 +438,7 @@ fn extension_adding_sequence() -> Result<(), Box> { Ok(()) } -fn make_opaque(extension: impl Into, signature: FunctionType) -> ops::LeafOp { +fn make_opaque(extension: impl Into, signature: FunctionType) -> CustomOp { let opaque = ops::custom::OpaqueOp::new(extension.into(), "", "".into(), vec![], signature); ops::custom::ExternalOp::from(opaque).into() } @@ -930,23 +930,21 @@ fn plus_on_self() -> Result<(), Box> { // While https://github.com/CQCL/hugr/issues/388 is unsolved, // most operations have empty extension_reqs (not including their own extension). // Define some that do. - let binop: LeafOp = ExternalOp::Opaque(OpaqueOp::new( + let binop = CustomOp::new(ExternalOp::Opaque(OpaqueOp::new( ext.clone(), "2qb_op", String::new(), vec![], ft, - )) - .into(); + ))); let unary_sig = FunctionType::new_endo(type_row![QB_T]).with_extension_delta(ext.clone()); - let unop: LeafOp = ExternalOp::Opaque(OpaqueOp::new( + let unop = CustomOp::new(ExternalOp::Opaque(OpaqueOp::new( ext, "1qb_op", String::new(), vec![], unary_sig, - )) - .into(); + ))); // Constrain q1,q2 as PLUS(ext1, inputs): let [q1, q2] = dfg .add_dataflow_op(binop.clone(), dfg.input_wires())? @@ -1020,7 +1018,7 @@ fn simple_funcdefn() -> Result<(), Box> { let [w] = func_builder.input_wires_arr(); let lift = func_builder.add_dataflow_op( - ops::LeafOp::Lift { + Lift { type_row: type_row![NAT], new_extension: A, }, @@ -1045,7 +1043,7 @@ fn funcdefn_signature_mismatch() -> Result<(), Box> { let [w] = func_builder.input_wires_arr(); let lift = func_builder.add_dataflow_op( - ops::LeafOp::Lift { + Lift { type_row: type_row![NAT], new_extension: B, }, diff --git a/hugr/src/extension/op_def.rs b/hugr/src/extension/op_def.rs index 9c9510e80..88b09d2fc 100644 --- a/hugr/src/extension/op_def.rs +++ b/hugr/src/extension/op_def.rs @@ -477,7 +477,7 @@ mod test { use crate::extension::{ExtensionRegistry, ExtensionSet, PRELUDE}; use crate::extension::{SignatureError, EMPTY_REG, PRELUDE_REGISTRY}; use crate::ops::custom::ExternalOp; - use crate::ops::LeafOp; + use crate::ops::CustomOp; use crate::std_extensions::collections::{EXTENSION, LIST_TYPENAME}; use crate::types::Type; use crate::types::{type_param::TypeParam, FunctionType, PolyFuncType, TypeArg, TypeBound}; @@ -513,7 +513,7 @@ mod test { Type::new_extension(list_def.instantiate(vec![TypeArg::Type { ty: USIZE_T }])?); let mut dfg = DFGBuilder::new(FunctionType::new_endo(vec![list_usize]))?; let rev = dfg.add_dataflow_op( - LeafOp::from(ExternalOp::Extension( + CustomOp::new(ExternalOp::Extension( e.instantiate_extension_op(&OP_NAME, vec![TypeArg::Type { ty: USIZE_T }], ®) .unwrap(), )), diff --git a/hugr/src/extension/prelude.rs b/hugr/src/extension/prelude.rs index cdccfdecd..f160325b7 100644 --- a/hugr/src/extension/prelude.rs +++ b/hugr/src/extension/prelude.rs @@ -3,11 +3,11 @@ use lazy_static::lazy_static; use smol_str::SmolStr; +use crate::ops::CustomOp; use crate::types::SumType; use crate::{ extension::{ExtensionId, TypeDefBound}, ops::constant::CustomConst, - ops::LeafOp, type_row, types::{ type_param::{TypeArg, TypeParam}, @@ -134,7 +134,7 @@ pub const NEW_ARRAY_OP_ID: &str = "new_array"; pub const PANIC_OP_ID: &str = "panic"; /// Initialize a new array op of element type `element_ty` of length `size` -pub fn new_array_op(element_ty: Type, size: u64) -> LeafOp { +pub fn new_array_op(element_ty: Type, size: u64) -> CustomOp { PRELUDE .instantiate_extension_op( NEW_ARRAY_OP_ID, diff --git a/hugr/src/extension/simple_op.rs b/hugr/src/extension/simple_op.rs index ff4b62778..e7fbda3a7 100644 --- a/hugr/src/extension/simple_op.rs +++ b/hugr/src/extension/simple_op.rs @@ -94,7 +94,7 @@ pub trait MakeExtensionOp: OpName { where Self: Sized, { - let ext: &ExtensionOp = op.as_leaf_op()?.as_extension_op()?; + let ext: &ExtensionOp = op.as_custom_op()?.as_ref().as_extension_op()?; Self::from_extension_op(ext).ok() } diff --git a/hugr/src/hugr.rs b/hugr/src/hugr.rs index 9a60cf90d..feb57ae16 100644 --- a/hugr/src/hugr.rs +++ b/hugr/src/hugr.rs @@ -374,7 +374,7 @@ mod test { use crate::builder::test::closed_dfg_root_hugr; use crate::extension::ExtensionSet; use crate::hugr::HugrMut; - use crate::ops::LeafOp; + use crate::ops::Lift; use crate::type_row; use crate::types::{FunctionType, Type}; @@ -387,7 +387,7 @@ mod test { let [input, output] = hugr.get_io(hugr.root()).unwrap(); let lift = hugr.add_node_with_parent( hugr.root(), - LeafOp::Lift { + Lift { type_row: type_row![BIT], new_extension: "R".try_into().unwrap(), }, diff --git a/hugr/src/hugr/hugrmut.rs b/hugr/src/hugr/hugrmut.rs index a0be5aade..ac29f6b6c 100644 --- a/hugr/src/hugr/hugrmut.rs +++ b/hugr/src/hugr/hugrmut.rs @@ -676,7 +676,7 @@ mod test { extension::prelude::USIZE_T, extension::PRELUDE_REGISTRY, macros::type_row, - ops::{self, dataflow::IOTrait, LeafOp}, + ops::{self, dataflow::IOTrait, Noop}, types::{FunctionType, Type}, }; @@ -704,7 +704,7 @@ mod test { let f_in = hugr.add_node_with_parent(f, NodeType::new_pure(ops::Input::new(type_row![NAT]))); let f_out = hugr.add_node_with_parent(f, ops::Output::new(type_row![NAT, NAT])); - let noop = hugr.add_node_with_parent(f, LeafOp::Noop { ty: NAT }); + let noop = hugr.add_node_with_parent(f, Noop { ty: NAT }); hugr.connect(f_in, 0, noop, 0); hugr.connect(noop, 0, f_out, 0); diff --git a/hugr/src/hugr/rewrite/consts.rs b/hugr/src/hugr/rewrite/consts.rs index 3fbd0aabd..129972867 100644 --- a/hugr/src/hugr/rewrite/consts.rs +++ b/hugr/src/hugr/rewrite/consts.rs @@ -119,7 +119,7 @@ mod test { PRELUDE_REGISTRY, }, hugr::HugrMut, - ops::{handle::NodeHandle, LeafOp}, + ops::{handle::NodeHandle, MakeTuple}, type_row, types::FunctionType, }; @@ -133,7 +133,7 @@ mod test { let load_1 = dfg_build.load_const(&con_node); let load_2 = dfg_build.load_const(&con_node); let tup = dfg_build.add_dataflow_op( - LeafOp::MakeTuple { + MakeTuple { tys: type_row![USIZE_T, USIZE_T], }, [load_1, load_2], diff --git a/hugr/src/hugr/rewrite/inline_dfg.rs b/hugr/src/hugr/rewrite/inline_dfg.rs index e5a6c4062..52a7cfd3f 100644 --- a/hugr/src/hugr/rewrite/inline_dfg.rs +++ b/hugr/src/hugr/rewrite/inline_dfg.rs @@ -139,7 +139,7 @@ mod test { use crate::hugr::rewrite::inline_dfg::InlineDFGError; use crate::hugr::HugrMut; use crate::ops::handle::{DfgID, NodeHandle}; - use crate::ops::{Const, LeafOp}; + use crate::ops::{Const, Lift, OpType}; use crate::std_extensions::arithmetic::float_types; use crate::std_extensions::arithmetic::int_ops::{self, IntOpDef}; use crate::std_extensions::arithmetic::int_types::{self, ConstIntU}; @@ -157,7 +157,7 @@ mod test { } fn extension_ops(h: &impl HugrView) -> Vec { h.nodes() - .filter(|n| matches!(h.get_optype(*n).as_leaf_op(), Some(LeafOp::CustomOp(_)))) + .filter(|n| matches!(h.get_optype(*n), OpType::CustomOp(_))) .collect() } @@ -187,7 +187,7 @@ mod test { let c1 = d.add_load_const(cst); let [lifted] = d .add_dataflow_op( - LeafOp::Lift { + Lift { type_row: vec![int_ty.clone()].into(), new_extension: int_ops::EXTENSION_ID, }, diff --git a/hugr/src/hugr/rewrite/insert_identity.rs b/hugr/src/hugr/rewrite/insert_identity.rs index ee61db971..1ea25f530 100644 --- a/hugr/src/hugr/rewrite/insert_identity.rs +++ b/hugr/src/hugr/rewrite/insert_identity.rs @@ -3,7 +3,7 @@ use std::iter; use crate::hugr::{HugrMut, Node}; -use crate::ops::{LeafOp, OpTag, OpTrait}; +use crate::ops::{Noop, OpTag, OpTrait}; use crate::types::EdgeKind; use crate::{HugrView, IncomingPort}; @@ -79,7 +79,7 @@ impl Rewrite for IdentityInsertion { if !OpTag::DataflowParent.is_superset(h.get_optype(parent).tag()) { return Err(IdentityInsertionError::InvalidParentNode); } - let new_node = h.add_node_with_parent(parent, LeafOp::Noop { ty }); + let new_node = h.add_node_with_parent(parent, Noop { ty }); h.connect(pre_node, pre_port, new_node, 0); h.connect(new_node, 0, self.post_node, self.post_port); @@ -124,9 +124,9 @@ mod tests { assert_eq!(h.node_count(), 7); - let noop: LeafOp = h.get_optype(noop_node).clone().try_into().unwrap(); + let noop: Noop = h.get_optype(noop_node).clone().try_into().unwrap(); - assert_eq!(noop, LeafOp::Noop { ty: QB_T }); + assert_eq!(noop, Noop { ty: QB_T }); h.update_validate(&PRELUDE_REGISTRY).unwrap(); } diff --git a/hugr/src/hugr/rewrite/replace.rs b/hugr/src/hugr/rewrite/replace.rs index 972a81440..acd47db5c 100644 --- a/hugr/src/hugr/rewrite/replace.rs +++ b/hugr/src/hugr/rewrite/replace.rs @@ -458,7 +458,7 @@ mod test { use crate::ops::custom::{ExternalOp, OpaqueOp}; use crate::ops::dataflow::DataflowOpTrait; use crate::ops::handle::{BasicBlockID, ConstID, NodeHandle}; - use crate::ops::{self, Case, DataflowBlock, LeafOp, OpTag, OpType, DFG}; + use crate::ops::{self, Case, CustomOp, DataflowBlock, OpTag, OpType, DFG}; use crate::std_extensions::collections; use crate::types::{FunctionType, Type, TypeArg, TypeRow}; use crate::{type_row, Direction, Hugr, HugrView, OutgoingPort}; @@ -477,11 +477,11 @@ mod test { .instantiate([TypeArg::Type { ty: USIZE_T }]) .unwrap(), ); - let pop: LeafOp = collections::EXTENSION + let pop: CustomOp = collections::EXTENSION .instantiate_extension_op("pop", [TypeArg::Type { ty: USIZE_T }], ®) .unwrap() .into(); - let push: LeafOp = collections::EXTENSION + let push: CustomOp = collections::EXTENSION .instantiate_extension_op("push", [TypeArg::Type { ty: USIZE_T }], ®) .unwrap() .into(); @@ -650,7 +650,7 @@ mod test { fn test_invalid() -> Result<(), Box> { let utou = FunctionType::new_endo(vec![USIZE_T]); let mk_op = |s| { - LeafOp::from(ExternalOp::Opaque(OpaqueOp::new( + CustomOp::new(ExternalOp::Opaque(OpaqueOp::new( ExtensionId::new("unknown_ext").unwrap(), s, String::new(), diff --git a/hugr/src/hugr/rewrite/simple_replace.rs b/hugr/src/hugr/rewrite/simple_replace.rs index ebee0d1bf..4fd64795b 100644 --- a/hugr/src/hugr/rewrite/simple_replace.rs +++ b/hugr/src/hugr/rewrite/simple_replace.rs @@ -209,7 +209,7 @@ pub(in crate::hugr::rewrite) mod test { use crate::hugr::{Hugr, HugrMut, Rewrite}; use crate::ops::dataflow::DataflowOpTrait; use crate::ops::OpTag; - use crate::ops::{OpTrait, OpType}; + use crate::ops::OpTrait; use crate::std_extensions::logic::test::and_op; use crate::type_row; use crate::types::{FunctionType, Type}; @@ -337,7 +337,7 @@ pub(in crate::hugr::rewrite) mod test { // 1. Locate the CX and its successor H's in h let h_node_cx: Node = h .nodes() - .find(|node: &Node| *h.get_optype(*node) == OpType::LeafOp(cx_gate())) + .find(|node: &Node| *h.get_optype(*node) == cx_gate().into()) .unwrap(); let (h_node_h0, h_node_h1) = h.output_neighbours(h_node_cx).collect_tuple().unwrap(); let s: Vec = vec![h_node_cx, h_node_h0, h_node_h1].into_iter().collect(); @@ -347,7 +347,7 @@ pub(in crate::hugr::rewrite) mod test { // 3.1. Locate the CX and its predecessor H's in n let n_node_cx = n .nodes() - .find(|node: &Node| *n.get_optype(*node) == OpType::LeafOp(cx_gate())) + .find(|node: &Node| *n.get_optype(*node) == cx_gate().into()) .unwrap(); let (n_node_h0, n_node_h1) = n.input_neighbours(n_node_cx).collect_tuple().unwrap(); // 3.2. Locate the ports we need to specify as "glue" in n @@ -415,7 +415,7 @@ pub(in crate::hugr::rewrite) mod test { // 1. Locate the CX in h let h_node_cx: Node = h .nodes() - .find(|node: &Node| *h.get_optype(*node) == OpType::LeafOp(cx_gate())) + .find(|node: &Node| *h.get_optype(*node) == cx_gate().into()) .unwrap(); let s: Vec = vec![h_node_cx].into_iter().collect(); // 2. Construct a new DFG-rooted hugr for the replacement diff --git a/hugr/src/hugr/serialize.rs b/hugr/src/hugr/serialize.rs index f1253a0a4..fdf8c8270 100644 --- a/hugr/src/hugr/serialize.rs +++ b/hugr/src/hugr/serialize.rs @@ -263,7 +263,7 @@ pub mod test { use crate::hugr::hugrmut::sealed::HugrMutInternals; use crate::hugr::NodeType; use crate::ops::custom::{ExtensionOp, OpaqueOp}; - use crate::ops::{dataflow::IOTrait, Input, LeafOp, Module, Output, DFG}; + use crate::ops::{dataflow::IOTrait, Input, Module, Noop, Output, DFG}; use crate::std_extensions::arithmetic::float_ops::FLOAT_OPS_REGISTRY; use crate::std_extensions::arithmetic::float_types::{ConstF64, FLOAT64_TYPE}; use crate::std_extensions::logic::NotOp; @@ -347,9 +347,12 @@ pub mod test { for node in new_hugr.nodes() { let new_op = new_hugr.get_optype(node); let old_op = h_canon.get_optype(node); - if let OpType::LeafOp(LeafOp::CustomOp(new_ext)) = new_op { - if let OpType::LeafOp(LeafOp::CustomOp(old_ext)) = old_op { - assert_eq!(new_ext.clone().as_opaque(), old_ext.clone().as_opaque()); + if let OpType::CustomOp(new_op) = new_op { + if let OpType::CustomOp(old_op) = old_op { + assert_eq!( + new_op.as_ref().clone().into_opaque(), + old_op.as_ref().clone().into_opaque() + ); } else { panic!("Expected old_op to be a custom op"); } @@ -444,7 +447,7 @@ pub mod test { .map(|in_wire| { f_build .add_dataflow_op( - LeafOp::Noop { + Noop { ty: f_build.get_wire_type(in_wire).unwrap(), }, [in_wire], @@ -469,7 +472,7 @@ pub mod test { let mut params: [_; 2] = dfg.input_wires_arr(); for p in params.iter_mut() { *p = dfg - .add_dataflow_op(LeafOp::Noop { ty: BOOL_T }, [*p]) + .add_dataflow_op(Noop { ty: BOOL_T }, [*p]) .unwrap() .out_wire(0); } @@ -506,7 +509,7 @@ pub mod test { fn function_type() -> Result<(), Box> { let fn_ty = Type::new_function(FunctionType::new_endo(type_row![BOOL_T])); let mut bldr = DFGBuilder::new(FunctionType::new_endo(vec![fn_ty.clone()]))?; - let op = bldr.add_dataflow_op(LeafOp::Noop { ty: fn_ty }, bldr.input_wires())?; + let op = bldr.add_dataflow_op(Noop { ty: fn_ty }, bldr.input_wires())?; let h = bldr.finish_prelude_hugr_with_outputs(op.outputs())?; check_hugr_roundtrip(&h); diff --git a/hugr/src/hugr/validate.rs b/hugr/src/hugr/validate.rs index 6853746ca..55f0487b6 100644 --- a/hugr/src/hugr/validate.rs +++ b/hugr/src/hugr/validate.rs @@ -525,10 +525,11 @@ impl<'a, 'b> ValidationContext<'a, 'b> { // The op_type must be defined only in terms of type variables defined outside the node // TODO consider turning this match into a trait method? match op_type { - OpType::LeafOp(crate::ops::LeafOp::CustomOp(b)) => { + OpType::CustomOp(b) => { // Try to resolve serialized names to actual OpDefs in Extensions. let temp: ExternalOp; - let resolved = match &**b { + let external = b.as_ref(); + let resolved = match external { ExternalOp::Opaque(op) => { // If resolve_extension_ops has been called first, this would always return Ok(None) match resolve_opaque_op(node, op, self.extension_registry)? { @@ -536,10 +537,10 @@ impl<'a, 'b> ValidationContext<'a, 'b> { temp = ExternalOp::Extension(exten); &temp } - None => &**b, + None => external, } } - ExternalOp::Extension(_) => &**b, + ExternalOp::Extension(_) => external, }; // Check TypeArgs are valid, and if we can, fit the declared TypeParams match resolved { @@ -697,7 +698,7 @@ pub enum ValidationError { SignatureError { node: Node, cause: SignatureError }, /// Error in a [CustomOp] serialized as an [Opaque] /// - /// [CustomOp]: crate::ops::LeafOp::CustomOp + /// [CustomOp]: crate::ops::CustomOp /// [Opaque]: crate::ops::custom::ExternalOp::Opaque #[error(transparent)] CustomOpError(#[from] CustomOpError), diff --git a/hugr/src/hugr/validate/test.rs b/hugr/src/hugr/validate/test.rs index 0e1ecaee0..04e6958c1 100644 --- a/hugr/src/hugr/validate/test.rs +++ b/hugr/src/hugr/validate/test.rs @@ -11,7 +11,7 @@ use crate::extension::{Extension, ExtensionId, TypeDefBound, EMPTY_REG, PRELUDE_ use crate::hugr::hugrmut::sealed::HugrMutInternals; use crate::hugr::{HugrMut, NodeType}; use crate::ops::dataflow::IOTrait; -use crate::ops::{self, Const, LeafOp, OpType}; +use crate::ops::{self, Const, Noop, OpType}; use crate::std_extensions::logic::test::{and_op, or_op}; use crate::std_extensions::logic::{self, NotOp}; use crate::types::type_param::{TypeArg, TypeArgError, TypeParam}; @@ -45,7 +45,7 @@ fn make_simple_hugr(copies: usize) -> (Hugr, Node) { fn add_df_children(b: &mut Hugr, parent: Node, copies: usize) -> (Node, Node, Node) { let input = b.add_node_with_parent(parent, ops::Input::new(type_row![BOOL_T])); let output = b.add_node_with_parent(parent, ops::Output::new(vec![BOOL_T; copies])); - let copy = b.add_node_with_parent(parent, LeafOp::Noop { ty: BOOL_T }); + let copy = b.add_node_with_parent(parent, Noop { ty: BOOL_T }); b.connect(input, 0, copy, 0); for i in 0..copies { @@ -91,7 +91,7 @@ fn invalid_root() { #[test] fn leaf_root() { - let leaf_op: OpType = LeafOp::Noop { ty: USIZE_T }.into(); + let leaf_op: OpType = Noop { ty: USIZE_T }.into(); let b = Hugr::new(NodeType::new_pure(leaf_op)); assert_eq!(b.validate(&EMPTY_REG), Ok(())); @@ -173,7 +173,7 @@ fn df_children_restrictions() { .unwrap(); // Replace the output operation of the df subgraph with a copy - b.replace_op(output, NodeType::new_pure(LeafOp::Noop { ty: NAT })) + b.replace_op(output, NodeType::new_pure(Noop { ty: NAT })) .unwrap(); assert_matches!( b.validate(&EMPTY_REG), @@ -716,7 +716,7 @@ mod extension_tests { let lift = hugr.add_node_with_parent( hugr.root(), - NodeType::new_pure(ops::LeafOp::Lift { + NodeType::new_pure(ops::Lift { type_row: type_row![USIZE_T], new_extension: XB, }), diff --git a/hugr/src/hugr/views/root_checked.rs b/hugr/src/hugr/views/root_checked.rs index 39aa332a8..8bde3c587 100644 --- a/hugr/src/hugr/views/root_checked.rs +++ b/hugr/src/hugr/views/root_checked.rs @@ -74,7 +74,7 @@ mod test { use crate::hugr::hugrmut::sealed::HugrMutInternals; use crate::hugr::{HugrError, HugrMut, NodeType}; use crate::ops::handle::{BasicBlockID, CfgID, DataflowParentID, DfgID}; - use crate::ops::{DataflowBlock, LeafOp, OpTag}; + use crate::ops::{DataflowBlock, MakeTuple, OpTag}; use crate::{ops, type_row, types::FunctionType, Hugr, HugrView}; #[test] @@ -129,7 +129,7 @@ mod test { let mut bb_v = RootChecked::<_, BasicBlockID>::try_new(dfp_v).unwrap(); // And it's a HugrMut: - let nodetype = NodeType::new_pure(LeafOp::MakeTuple { tys: type_row![] }); + let nodetype = NodeType::new_pure(MakeTuple { tys: type_row![] }); bb_v.add_node_with_parent(bb_v.root(), nodetype); } } diff --git a/hugr/src/lib.rs b/hugr/src/lib.rs index e83ea9fbd..2b108cdaa 100644 --- a/hugr/src/lib.rs +++ b/hugr/src/lib.rs @@ -43,7 +43,7 @@ //! prelude::{BOOL_T, QB_T}, //! ExtensionId, ExtensionRegistry, PRELUDE, //! }, -//! ops::LeafOp, +//! ops::CustomOp, //! type_row, //! types::{FunctionType, PolyFuncType}, //! Extension, @@ -89,21 +89,21 @@ //! ExtensionRegistry::try_new([EXTENSION.to_owned(), PRELUDE.to_owned()]).unwrap(); //! //! } -//! fn get_gate(gate_name: &str) -> LeafOp { +//! fn get_gate(gate_name: &str) -> CustomOp { //! EXTENSION //! .instantiate_extension_op(gate_name, [], ®) //! .unwrap() //! .into() //! } -//! pub fn h_gate() -> LeafOp { +//! pub fn h_gate() -> CustomOp { //! get_gate("H") //! } //! -//! pub fn cx_gate() -> LeafOp { +//! pub fn cx_gate() -> CustomOp { //! get_gate("CX") //! } //! -//! pub fn measure() -> LeafOp { +//! pub fn measure() -> CustomOp { //! get_gate("Measure") //! } //! } diff --git a/hugr/src/ops.rs b/hugr/src/ops.rs index 7145b35e1..ec20b94f7 100644 --- a/hugr/src/ops.rs +++ b/hugr/src/ops.rs @@ -23,7 +23,7 @@ use enum_dispatch::enum_dispatch; pub use constant::Const; pub use controlflow::{BasicBlock, Case, Conditional, DataflowBlock, ExitBlock, TailLoop, CFG}; pub use dataflow::{Call, CallIndirect, DataflowParent, Input, LoadConstant, Output, DFG}; -pub use leaf::LeafOp; +pub use leaf::{CustomOp, Lift, MakeTuple, Noop, Tag, UnpackTuple}; pub use module::{AliasDecl, AliasDefn, FuncDecl, FuncDefn, Module}; pub use tag::OpTag; @@ -47,7 +47,12 @@ pub enum OpType { CallIndirect, LoadConstant, DFG, - LeafOp, + CustomOp, + Noop, + MakeTuple, + UnpackTuple, + Tag, + Lift, DataflowBlock, ExitBlock, TailLoop, @@ -100,7 +105,12 @@ impl_op_ref_try_into!(Call); impl_op_ref_try_into!(CallIndirect); impl_op_ref_try_into!(LoadConstant); impl_op_ref_try_into!(DFG, dfg); -impl_op_ref_try_into!(LeafOp); +impl_op_ref_try_into!(CustomOp); +impl_op_ref_try_into!(Noop); +impl_op_ref_try_into!(MakeTuple); +impl_op_ref_try_into!(UnpackTuple); +impl_op_ref_try_into!(Tag); +impl_op_ref_try_into!(Lift); impl_op_ref_try_into!(DataflowBlock); impl_op_ref_try_into!(ExitBlock); impl_op_ref_try_into!(TailLoop); @@ -402,7 +412,12 @@ impl OpParent for Output {} impl OpParent for Call {} impl OpParent for CallIndirect {} impl OpParent for LoadConstant {} -impl OpParent for LeafOp {} +impl OpParent for CustomOp {} +impl OpParent for Noop {} +impl OpParent for MakeTuple {} +impl OpParent for UnpackTuple {} +impl OpParent for Tag {} +impl OpParent for Lift {} impl OpParent for TailLoop {} impl OpParent for CFG {} impl OpParent for Conditional {} diff --git a/hugr/src/ops/custom.rs b/hugr/src/ops/custom.rs index d4e4c88a6..287b90959 100644 --- a/hugr/src/ops/custom.rs +++ b/hugr/src/ops/custom.rs @@ -12,7 +12,7 @@ use crate::{ops, Hugr, IncomingPort, Node}; use super::dataflow::DataflowOpTrait; use super::tag::OpTag; -use super::{LeafOp, OpTrait, OpType}; +use super::{CustomOp, OpTrait, OpType}; /// An instantiation of an operation (declared by a extension) with values for the type arguments #[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] @@ -46,8 +46,17 @@ impl ExternalOp { qualify_name(res_id, op_name) } - /// Downgrades this ExternalOp into an OpaqueOp - pub fn as_opaque(self) -> OpaqueOp { + /// If the operation is an instance of [ExtensionOp], return a reference to it. + /// If the operation is opaque, return None. + pub fn as_extension_op(&self) -> Option<&ExtensionOp> { + match self { + ExternalOp::Extension(e) => Some(e), + ExternalOp::Opaque(_) => None, + } + } + + /// Downgrades this [ExternalOp] into an OpaqueOp. + pub fn into_opaque(self) -> OpaqueOp { match self { Self::Opaque(op) => op, Self::Extension(op) => op.into(), @@ -70,16 +79,15 @@ impl From for ExternalOp { } } -impl From for LeafOp { +impl From for CustomOp { fn from(value: ExternalOp) -> Self { - LeafOp::CustomOp(Box::new(value)) + Self::new(value) } } impl From for OpType { fn from(value: ExternalOp) -> Self { - let leaf: LeafOp = value.into(); - leaf.into() + OpType::CustomOp(CustomOp::new(value)) } } @@ -142,6 +150,23 @@ impl ExtensionOp { pub fn constant_fold(&self, consts: &[(IncomingPort, ops::Const)]) -> ConstFoldResult { self.def().constant_fold(self.args(), consts) } + + /// Creates a new [`OpaqueOp`] as a downgraded version of this + /// [`ExtensionOp`]. + /// + /// Regenerating the [`ExtensionOp`] back from the [`OpaqueOp`] requires a + /// registry with the appropriate extension. See [`resolve_opaque_op`]. + /// + /// For a non-cloning version of this operation, see [`ExternalOp::from`]. + pub fn make_opaque(&self) -> OpaqueOp { + OpaqueOp { + extension: self.def.extension().clone(), + op_name: self.def.name().clone(), + description: self.def.description().into(), + args: self.args.clone(), + signature: self.signature.clone(), + } + } } impl From for OpaqueOp { @@ -161,16 +186,15 @@ impl From for OpaqueOp { } } -impl From for LeafOp { +impl From for CustomOp { fn from(value: ExtensionOp) -> Self { - LeafOp::CustomOp(Box::new(ExternalOp::Extension(value))) + CustomOp::new(ExternalOp::Extension(value)) } } impl From for OpType { fn from(value: ExtensionOp) -> Self { - let leaf: LeafOp = value.into(); - leaf.into() + OpType::CustomOp(value.into()) } } @@ -244,16 +268,15 @@ impl OpaqueOp { } } -impl From for LeafOp { +impl From for CustomOp { fn from(value: OpaqueOp) -> Self { - LeafOp::CustomOp(Box::new(ExternalOp::Opaque(value))) + CustomOp::new(ExternalOp::Opaque(value)) } } impl From for OpType { fn from(value: OpaqueOp) -> Self { - let leaf: LeafOp = value.into(); - leaf.into() + OpType::CustomOp(value.into()) } } @@ -270,14 +293,13 @@ impl DataflowOpTrait for OpaqueOp { } /// Resolve serialized names of operations into concrete implementation (OpDefs) where possible -#[allow(dead_code)] pub fn resolve_extension_ops( h: &mut Hugr, extension_registry: &ExtensionRegistry, ) -> Result<(), CustomOpError> { let mut replacements = Vec::new(); for n in h.nodes() { - if let OpType::LeafOp(LeafOp::CustomOp(op)) = h.get_optype(n) { + if let OpType::CustomOp(op) = h.get_optype(n) { if let ExternalOp::Opaque(opaque) = op.as_ref() { if let Some(resolved) = resolve_opaque_op(n, opaque, extension_registry)? { replacements.push((n, resolved)) @@ -287,8 +309,7 @@ pub fn resolve_extension_ops( } // Only now can we perform the replacements as the 'for' loop was borrowing 'h' preventing use from using it mutably for (n, op) in replacements { - let leaf: LeafOp = op.into(); - let node_type = NodeType::new(leaf, h.get_nodetype(n).input_extensions().cloned()); + let node_type = NodeType::new(op, h.get_nodetype(n).input_extensions().cloned()); debug_assert_eq!(h.get_optype(n).tag(), OpTag::Leaf); debug_assert_eq!(node_type.tag(), OpTag::Leaf); h.replace_op(n, node_type).unwrap(); diff --git a/hugr/src/ops/leaf.rs b/hugr/src/ops/leaf.rs index 878fcec2a..c667abfc5 100644 --- a/hugr/src/ops/leaf.rs +++ b/hugr/src/ops/leaf.rs @@ -1,10 +1,10 @@ -//! Definition of the leaf operations. +//! Definition of dataflow operations with no children. use smol_str::SmolStr; -use super::custom::{ExtensionOp, ExternalOp}; +use super::custom::ExternalOp; use super::dataflow::DataflowOpTrait; -use super::{OpName, OpTag}; +use super::{impl_op_name, OpName, OpTag}; use crate::extension::ExtensionSet; @@ -13,122 +13,221 @@ use crate::{ types::{EdgeKind, FunctionType, Type, TypeRow}, }; -/// Dataflow operations with no children. -#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)] -#[non_exhaustive] -#[serde(tag = "lop")] -pub enum LeafOp { - /// A user-defined operation that can be downcasted by the extensions that - /// define it. - CustomOp(Box), - - /// A no-op operation. - Noop { - /// The type of edges connecting the Noop. - ty: Type, - }, - /// An operation that packs all its inputs into a tuple. - MakeTuple { - ///Tuple element types. - tys: TypeRow, - }, - /// An operation that unpacks a tuple into its components. - UnpackTuple { - ///Tuple element types. - tys: TypeRow, - }, - /// An operation that creates a tagged sum value from one of its variants. - Tag { - /// The variant to create. - tag: usize, - /// The variants of the sum type. - variants: Vec, - }, - /// A node which adds a extension req to the types of the wires it is passed - /// It has no effect on the values passed along the edge - Lift { - /// The types of the edges - type_row: TypeRow, - /// The extensions which we're adding to the inputs - new_extension: ExtensionId, - }, -} +/// A user-defined operation defined in an extension. +/// +/// Any custom operation can be encoded as a serializable [`OpaqueOp`]. If the +/// operation's extension is loaded in the current context, the operation can be +/// resolved into an [`ExtensionOp`] containing a reference to its definition. +/// +/// [`OpaqueOp`]: crate::ops::custom::OpaqueOp +/// [`ExtensionOp`]: crate::ops::custom::ExtensionOp +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +pub struct CustomOp(Box); -impl LeafOp { - /// If instance of [ExtensionOp] return a reference to it. - pub fn as_extension_op(&self) -> Option<&ExtensionOp> { - let LeafOp::CustomOp(ext) = self else { - return None; - }; +impl CustomOp { + /// Create a new custom operation. + pub fn new(op: ExternalOp) -> Self { + Self(Box::new(op)) + } +} - match ext.as_ref() { - ExternalOp::Extension(e) => Some(e), - ExternalOp::Opaque(_) => None, - } +impl AsRef for CustomOp { + fn as_ref(&self) -> &ExternalOp { + &self.0 } } -impl Default for LeafOp { +/// A no-op operation. +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct Noop { + /// The type of edges connecting the Noop. + pub ty: Type, +} + +impl Default for Noop { fn default() -> Self { - Self::Noop { ty: Type::UNIT } + Self { ty: Type::UNIT } } } -impl OpName for LeafOp { + +/// An operation that packs all its inputs into a tuple. +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct MakeTuple { + ///Tuple element types. + pub tys: TypeRow, +} + +/// An operation that unpacks a tuple into its components. +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct UnpackTuple { + ///Tuple element types. + pub tys: TypeRow, +} + +/// An operation that creates a tagged sum value from one of its variants. +#[derive(Debug, Clone, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct Tag { + /// The variant to create. + pub tag: usize, + /// The variants of the sum type. + pub variants: Vec, +} + +/// A node which adds a extension req to the types of the wires it is passed +/// It has no effect on the values passed along the edge +#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] +#[non_exhaustive] +pub struct Lift { + /// The types of the edges + pub type_row: TypeRow, + /// The extensions which we're adding to the inputs + pub new_extension: ExtensionId, +} + +impl OpName for CustomOp { /// The name of the operation. fn name(&self) -> SmolStr { - match self { - LeafOp::CustomOp(ext) => return ext.name(), - LeafOp::Noop { ty: _ } => "Noop", - LeafOp::MakeTuple { tys: _ } => "MakeTuple", - LeafOp::UnpackTuple { tys: _ } => "UnpackTuple", - LeafOp::Tag { .. } => "Tag", - LeafOp::Lift { .. } => "Lift", - } - .into() + self.0.name() + } +} +impl_op_name!(Noop); +impl_op_name!(MakeTuple); +impl_op_name!(UnpackTuple); +impl_op_name!(Tag); +impl_op_name!(Lift); + +impl DataflowOpTrait for CustomOp { + const TAG: OpTag = OpTag::Leaf; + + /// A human-readable description of the operation. + fn description(&self) -> &str { + self.0.description() + } + + /// The signature of the operation. + fn signature(&self) -> FunctionType { + self.0.signature() + } + + fn other_input(&self) -> Option { + Some(EdgeKind::StateOrder) + } + + fn other_output(&self) -> Option { + Some(EdgeKind::StateOrder) } } -// impl StaticTag for LeafOp { -// } +impl DataflowOpTrait for Noop { + const TAG: OpTag = OpTag::Leaf; + + /// A human-readable description of the operation. + fn description(&self) -> &str { + "Noop gate" + } -impl DataflowOpTrait for LeafOp { + /// The signature of the operation. + fn signature(&self) -> FunctionType { + FunctionType::new(vec![self.ty.clone()], vec![self.ty.clone()]) + } + + fn other_input(&self) -> Option { + Some(EdgeKind::StateOrder) + } + + fn other_output(&self) -> Option { + Some(EdgeKind::StateOrder) + } +} + +impl DataflowOpTrait for MakeTuple { const TAG: OpTag = OpTag::Leaf; + + /// A human-readable description of the operation. + fn description(&self) -> &str { + "MakeTuple operation" + } + + /// The signature of the operation. + fn signature(&self) -> FunctionType { + FunctionType::new(self.tys.clone(), vec![Type::new_tuple(self.tys.clone())]) + } + + fn other_input(&self) -> Option { + Some(EdgeKind::StateOrder) + } + + fn other_output(&self) -> Option { + Some(EdgeKind::StateOrder) + } +} + +impl DataflowOpTrait for UnpackTuple { + const TAG: OpTag = OpTag::Leaf; + + /// A human-readable description of the operation. + fn description(&self) -> &str { + "UnpackTuple operation" + } + + /// The signature of the operation. + fn signature(&self) -> FunctionType { + FunctionType::new(vec![Type::new_tuple(self.tys.clone())], self.tys.clone()) + } + + fn other_input(&self) -> Option { + Some(EdgeKind::StateOrder) + } + + fn other_output(&self) -> Option { + Some(EdgeKind::StateOrder) + } +} + +impl DataflowOpTrait for Tag { + const TAG: OpTag = OpTag::Leaf; + + /// A human-readable description of the operation. + fn description(&self) -> &str { + "Tag Sum operation" + } + + /// The signature of the operation. + fn signature(&self) -> FunctionType { + FunctionType::new( + self.variants + .get(self.tag) + .expect("Not a valid tag") + .clone(), + vec![Type::new_sum(self.variants.clone())], + ) + } + + fn other_input(&self) -> Option { + Some(EdgeKind::StateOrder) + } + + fn other_output(&self) -> Option { + Some(EdgeKind::StateOrder) + } +} + +impl DataflowOpTrait for Lift { + const TAG: OpTag = OpTag::Leaf; + /// A human-readable description of the operation. fn description(&self) -> &str { - match self { - LeafOp::CustomOp(ext) => ext.description(), - LeafOp::Noop { ty: _ } => "Noop gate", - LeafOp::MakeTuple { tys: _ } => "MakeTuple operation", - LeafOp::UnpackTuple { tys: _ } => "UnpackTuple operation", - LeafOp::Tag { .. } => "Tag Sum operation", - LeafOp::Lift { .. } => "Add a extension requirement to an edge", - } + "Add a extension requirement to an edge" } /// The signature of the operation. fn signature(&self) -> FunctionType { - // Static signatures. The `TypeRow`s in the `FunctionType` use a - // copy-on-write strategy, so we can avoid unnecessary allocations. - - match self { - LeafOp::Noop { ty: typ } => FunctionType::new(vec![typ.clone()], vec![typ.clone()]), - LeafOp::CustomOp(ext) => ext.signature(), - LeafOp::MakeTuple { tys: types } => { - FunctionType::new(types.clone(), vec![Type::new_tuple(types.clone())]) - } - LeafOp::UnpackTuple { tys: types } => { - FunctionType::new(vec![Type::new_tuple(types.clone())], types.clone()) - } - LeafOp::Tag { tag, variants } => FunctionType::new( - variants.get(*tag).expect("Not a valid tag").clone(), - vec![Type::new_sum(variants.clone())], - ), - LeafOp::Lift { - type_row, - new_extension, - } => FunctionType::new(type_row.clone(), type_row.clone()) - .with_extension_delta(ExtensionSet::singleton(new_extension)), - } + FunctionType::new(self.type_row.clone(), self.type_row.clone()) + .with_extension_delta(ExtensionSet::singleton(&self.new_extension)) } fn other_input(&self) -> Option { diff --git a/hugr/src/ops/validate.rs b/hugr/src/ops/validate.rs index 15cc98e53..3a828a8d0 100644 --- a/hugr/src/ops/validate.rs +++ b/hugr/src/ops/validate.rs @@ -347,8 +347,8 @@ fn validate_cfg_edge(edge: ChildrenEdgeData) -> Result<(), EdgeValidationError> #[cfg(test)] mod test { use crate::extension::prelude::USIZE_T; + use crate::ops::dataflow::IOTrait; use crate::{ops, type_row}; - use crate::{ops::dataflow::IOTrait, ops::LeafOp}; use cool_asserts::assert_matches; use super::*; @@ -360,7 +360,7 @@ mod test { let input_node: OpType = ops::Input::new(in_types.clone()).into(); let output_node = ops::Output::new(out_types.clone()).into(); - let leaf_node = LeafOp::Noop { ty: USIZE_T }.into(); + let leaf_node = ops::Noop { ty: USIZE_T }.into(); // Well-formed dataflow sibling nodes. Check the input and output node signatures. let children = vec![ @@ -404,7 +404,8 @@ mod test { } use super::{ - AliasDecl, AliasDefn, Call, CallIndirect, Const, FuncDecl, Input, LeafOp, LoadConstant, Output, + AliasDecl, AliasDefn, Call, CallIndirect, Const, CustomOp, FuncDecl, Input, Lift, LoadConstant, + MakeTuple, Noop, Output, Tag, UnpackTuple, }; impl_validate_op!(FuncDecl); impl_validate_op!(AliasDecl); @@ -415,5 +416,10 @@ impl_validate_op!(Const); impl_validate_op!(Call); impl_validate_op!(LoadConstant); impl_validate_op!(CallIndirect); -impl_validate_op!(LeafOp); +impl_validate_op!(CustomOp); +impl_validate_op!(Noop); +impl_validate_op!(MakeTuple); +impl_validate_op!(UnpackTuple); +impl_validate_op!(Tag); +impl_validate_op!(Lift); impl_validate_op!(ExitBlock); diff --git a/hugr/src/utils.rs b/hugr/src/utils.rs index b8230de33..c84491ef3 100644 --- a/hugr/src/utils.rs +++ b/hugr/src/utils.rs @@ -105,7 +105,7 @@ pub(crate) mod test_quantum_extension { prelude::{BOOL_T, QB_T}, ExtensionId, ExtensionRegistry, PRELUDE, }, - ops::LeafOp, + ops::CustomOp, std_extensions::arithmetic::float_types, type_row, types::{FunctionType, PolyFuncType}, @@ -174,33 +174,33 @@ pub(crate) mod test_quantum_extension { static ref REG: ExtensionRegistry = ExtensionRegistry::try_new([EXTENSION.to_owned(), PRELUDE.to_owned(), float_types::EXTENSION.to_owned()]).unwrap(); } - fn get_gate(gate_name: &str) -> LeafOp { + fn get_gate(gate_name: &str) -> CustomOp { EXTENSION .instantiate_extension_op(gate_name, [], ®) .unwrap() .into() } - pub(crate) fn h_gate() -> LeafOp { + pub(crate) fn h_gate() -> CustomOp { get_gate("H") } - pub(crate) fn cx_gate() -> LeafOp { + pub(crate) fn cx_gate() -> CustomOp { get_gate("CX") } - pub(crate) fn measure() -> LeafOp { + pub(crate) fn measure() -> CustomOp { get_gate("Measure") } - pub(crate) fn rz_f64() -> LeafOp { + pub(crate) fn rz_f64() -> CustomOp { get_gate("RzF64") } - pub(crate) fn q_alloc() -> LeafOp { + pub(crate) fn q_alloc() -> CustomOp { get_gate("QAlloc") } - pub(crate) fn q_discard() -> LeafOp { + pub(crate) fn q_discard() -> CustomOp { get_gate("QDiscard") } } diff --git a/specification/hugr.md b/specification/hugr.md index 4d99bfb21..07eab9b09 100644 --- a/specification/hugr.md +++ b/specification/hugr.md @@ -1988,7 +1988,7 @@ The "Root" row of the table applies to whichever node is the HUGR root, including `Module`. | Node type | `Value` | `Order` | `Const` | `Function` | `ControlFlow` | `Hierarchy` | Children | -| -------------- | ------- | ------- |-------- | ---------- | ------------- | ----------- | -------- | +|----------------|---------|---------|---------|------------|---------------|-------------|----------| | Root | 0, 0 | 0, 0 | 0, 0 | 0, 0 | 0, 0 | 0, ✱ | | | `FuncDefn` | 0, 0 | 0, 0 | 0, 0 | 0, * | 0, 0 | 1, + | DSG | | `FuncDecl` | 0, 0 | 0, 0 | 0, 0 | 0, * | 0, 0 | 1, 0 | | @@ -1998,7 +1998,6 @@ including `Module`. | `LoadConstant` | 0, 1 | +, ✱ | 1, 0 | 0, 0 | 0, 0 | 1, 0 | | | `Input` | 0, ✱ | 0, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | | `Output` | ✱, 0 | ✱, 0 | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | -| `LeafOp` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | | `Call` | ✱, ✱ | ✱, ✱ | 0, 0 | 1, 0 | 0, 0 | 1, 0 | | | `DFG` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, + | DSG | | `CFG` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, + | CSG | @@ -2007,6 +2006,12 @@ including `Module`. | `TailLoop` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, + | DSG | | `Conditional` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, + | `Case` | | `Case` | 0, 0 | 0, 0 | 0, 0 | 0, 0 | 0, 0 | 1, + | DSG | +| `CustomOp` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | +| `Noop` | 1, 1 | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | +| `MakeTuple` | ✱, 1 | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | +| `UnpackTuple` | 1, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | +| `Tag` | 1, 1 | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | +| `Lift` | ✱, ✱ | ✱, ✱ | 0, 0 | 0, 0 | 0, 0 | 1, 0 | | ### Appendix 3: Binary `compute_signature` diff --git a/specification/schema/hugr_schema_v1.json b/specification/schema/hugr_schema_v1.json index 6b6731be5..e634d4076 100644 --- a/specification/schema/hugr_schema_v1.json +++ b/specification/schema/hugr_schema_v1.json @@ -255,14 +255,9 @@ "$ref": "#/$defs/ExtensionSet" }, "op": { - "const": "LeafOp", - "default": "LeafOp", - "title": "Op" - }, - "lop": { "const": "CustomOp", "default": "CustomOp", - "title": "Lop" + "title": "Op" }, "extension": { "title": "Extension", @@ -674,40 +669,40 @@ "title": "Input", "type": "object" }, - "LeafOp": { - "description": "A constant operation.", - "discriminator": { - "mapping": { - "CustomOp": "#/$defs/CustomOp", - "MakeTuple": "#/$defs/MakeTuple", - "Noop": "#/$defs/Noop", - "Tag": "#/$defs/Tag", - "TypeApply": "#/$defs/TypeApply", - "UnpackTuple": "#/$defs/UnpackTuple" - }, - "propertyName": "lop" - }, - "oneOf": [ - { - "$ref": "#/$defs/CustomOp" - }, - { - "$ref": "#/$defs/Noop" + "Lift": { + "description": "Fixes some TypeParams of a polymorphic type by providing TypeArgs.", + "properties": { + "parent": { + "title": "Parent", + "type": "integer" }, - { - "$ref": "#/$defs/MakeTuple" + "input_extensions": { + "$ref": "#/$defs/ExtensionSet" }, - { - "$ref": "#/$defs/UnpackTuple" + "op": { + "const": "Lift", + "default": "Lift", + "title": "Op" }, - { - "$ref": "#/$defs/Tag" + "type_row": { + "items": { + "$ref": "#/$defs/Type" + }, + "title": "Type Row", + "type": "array" }, - { - "$ref": "#/$defs/TypeApply" + "new_extension": { + "title": "New Extension", + "type": "string" } + }, + "required": [ + "parent", + "type_row", + "new_extension" ], - "title": "LeafOp" + "title": "Lift", + "type": "object" }, "ListParam": { "properties": { @@ -763,14 +758,9 @@ "$ref": "#/$defs/ExtensionSet" }, "op": { - "const": "LeafOp", - "default": "LeafOp", - "title": "Op" - }, - "lop": { "const": "MakeTuple", "default": "MakeTuple", - "title": "Lop" + "title": "Op" }, "tys": { "items": { @@ -819,14 +809,9 @@ "$ref": "#/$defs/ExtensionSet" }, "op": { - "const": "LeafOp", - "default": "LeafOp", - "title": "Op" - }, - "lop": { "const": "Noop", "default": "Noop", - "title": "Lop" + "title": "Op" }, "ty": { "$ref": "#/$defs/Type" @@ -849,17 +834,22 @@ "Case": "#/$defs/Case", "Conditional": "#/$defs/Conditional", "Const": "#/$defs/Const", + "CustomOp": "#/$defs/CustomOp", "DFG": "#/$defs/DFG", "DataflowBlock": "#/$defs/DataflowBlock", "ExitBlock": "#/$defs/ExitBlock", "FuncDecl": "#/$defs/FuncDecl", "FuncDefn": "#/$defs/FuncDefn", "Input": "#/$defs/Input", - "LeafOp": "#/$defs/LeafOp", + "Lift": "#/$defs/Lift", "LoadConstant": "#/$defs/LoadConstant", + "MakeTuple": "#/$defs/MakeTuple", "Module": "#/$defs/Module", + "Noop": "#/$defs/Noop", "Output": "#/$defs/Output", - "TailLoop": "#/$defs/TailLoop" + "Tag": "#/$defs/Tag", + "TailLoop": "#/$defs/TailLoop", + "UnpackTuple": "#/$defs/UnpackTuple" }, "propertyName": "op" }, @@ -910,7 +900,22 @@ "$ref": "#/$defs/LoadConstant" }, { - "$ref": "#/$defs/LeafOp" + "$ref": "#/$defs/CustomOp" + }, + { + "$ref": "#/$defs/Noop" + }, + { + "$ref": "#/$defs/MakeTuple" + }, + { + "$ref": "#/$defs/UnpackTuple" + }, + { + "$ref": "#/$defs/Tag" + }, + { + "$ref": "#/$defs/Lift" }, { "$ref": "#/$defs/DFG" @@ -1146,14 +1151,9 @@ "$ref": "#/$defs/ExtensionSet" }, "op": { - "const": "LeafOp", - "default": "LeafOp", - "title": "Op" - }, - "lop": { "const": "Tag", "default": "Tag", - "title": "Lop" + "title": "Op" }, "tag": { "title": "Tag", @@ -1315,62 +1315,6 @@ ], "title": "Type" }, - "TypeApplication": { - "description": "Records details of an application of a PolyFuncType to some TypeArgs and the result (a less-, but still potentially-, polymorphic type).", - "properties": { - "input": { - "$ref": "#/$defs/PolyFuncType" - }, - "args": { - "items": { - "$ref": "#/$defs/TypeTypeArg" - }, - "title": "Args", - "type": "array" - }, - "output": { - "$ref": "#/$defs/PolyFuncType" - } - }, - "required": [ - "input", - "args", - "output" - ], - "title": "TypeApplication", - "type": "object" - }, - "TypeApply": { - "description": "Fixes some TypeParams of a polymorphic type by providing TypeArgs.", - "properties": { - "parent": { - "title": "Parent", - "type": "integer" - }, - "input_extensions": { - "$ref": "#/$defs/ExtensionSet" - }, - "op": { - "const": "LeafOp", - "default": "LeafOp", - "title": "Op" - }, - "lop": { - "const": "TypeApply", - "default": "TypeApply", - "title": "Lop" - }, - "ta": { - "$ref": "#/$defs/TypeApplication" - } - }, - "required": [ - "parent", - "ta" - ], - "title": "TypeApply", - "type": "object" - }, "TypeArg": { "description": "A type argument.", "discriminator": { @@ -1523,14 +1467,9 @@ "$ref": "#/$defs/ExtensionSet" }, "op": { - "const": "LeafOp", - "default": "LeafOp", - "title": "Op" - }, - "lop": { "const": "UnpackTuple", "default": "UnpackTuple", - "title": "Lop" + "title": "Op" }, "tys": { "items": {