Skip to main content

hugr_core/extension/
simple_op.rs

1//! A trait that enum for op definitions that gathers up some shared functionality.
2
3use std::sync::{Arc, Weak};
4
5use strum::IntoEnumIterator;
6
7use crate::ops::{ExtensionOp, OpName, OpNameRef};
8use crate::types::type_param::TermKindError;
9use crate::{Extension, ops::OpType, types::TypeArg};
10
11use super::{ExtensionBuildError, ExtensionId, OpDef, SignatureError, op_def::SignatureFunc};
12use crate::ops::custom::qualify_name;
13use delegate::delegate;
14use thiserror::Error;
15
16/// Error loading operation.
17#[derive(Debug, Error, PartialEq, Clone)]
18#[error("{0}")]
19#[allow(missing_docs)]
20#[non_exhaustive]
21pub enum OpLoadError {
22    #[error("Op with name {0} is not a member of this set.")]
23    NotMember(String),
24    #[error("Type args invalid: {0}.")]
25    InvalidArgs(#[from] SignatureError),
26    #[error("OpDef belongs to extension {0}, expected {1}.")]
27    WrongExtension(ExtensionId, ExtensionId),
28}
29
30impl From<TermKindError> for OpLoadError {
31    fn from(value: TermKindError) -> Self {
32        SignatureError::from(value).into()
33    }
34}
35
36/// Traits implemented by types which can add themselves to [`Extension`]s as
37/// [`OpDef`]s or load themselves from an [`OpDef`].
38///
39/// Particularly useful with C-style enums that implement [`strum::IntoEnumIterator`],
40/// as then all definitions can be added to an extension at once.
41///
42/// [`MakeExtensionOp`] has a blanket impl for types that impl [`MakeOpDef`].
43pub trait MakeOpDef {
44    /// The [`OpDef::name`] which will be used when `Self`  is added to an [Extension]
45    /// or when `Self` is loaded from an [`OpDef`].
46    ///
47    /// This identifier must be unique within the extension with which the
48    /// [`OpDef`] is registered. An [`ExtensionOp`] instantiating this [`OpDef`] will
49    /// report `self.opdef_id()` as its [`ExtensionOp::unqualified_id`].
50    ///
51    /// [`MakeExtensionOp::op_id`] must match this function.
52    fn opdef_id(&self) -> OpName;
53
54    /// Try to load one of the operations of this set from an [`OpDef`].
55    fn from_def(op_def: &OpDef) -> Result<Self, OpLoadError>
56    where
57        Self: Sized;
58
59    /// The ID of the extension this operation is defined in.
60    fn extension(&self) -> ExtensionId;
61
62    /// Returns the qualified id of the operation. e.g. 'arithmetic.iadd'.
63    /// See [`MakeOpDef::opdef_id`] for more information.
64    fn qualified_opdef_id(&self) -> OpName {
65        qualify_name(&self.extension(), &self.opdef_id())
66    }
67
68    /// Returns a weak reference to the extension this operation is defined in.
69    fn extension_ref(&self) -> Weak<Extension>;
70
71    /// Compute the signature of the operation while the extension definition is being built.
72    ///
73    /// Requires a [`Weak`] reference to the extension defining the operation.
74    /// This method is intended to be used inside the closure passed to [`Extension::new_arc`],
75    /// and it is normally internally called by [`MakeOpDef::add_to_extension`].
76    fn init_signature(&self, extension_ref: &Weak<Extension>) -> SignatureFunc;
77
78    /// Return the signature (polymorphic function type) of the operation.
79    fn signature(&self) -> SignatureFunc {
80        self.init_signature(&self.extension_ref())
81    }
82
83    /// Description of the operation. By default, the same as `self.opdef_id()`.
84    fn description(&self) -> String {
85        self.opdef_id().to_string()
86    }
87
88    /// Edit the opdef before finalising. By default does nothing.
89    fn post_opdef(&self, _def: &mut OpDef) {}
90
91    /// Add an operation implemented as an [`MakeOpDef`], which can provide the data
92    /// required to define an [`OpDef`], to an extension.
93    ///
94    /// Requires a [`Weak`] reference to the extension defining the operation.
95    /// This method is intended to be used inside the closure passed to [`Extension::new_arc`].
96    fn add_to_extension(
97        &self,
98        extension: &mut Extension,
99        extension_ref: &Weak<Extension>,
100    ) -> Result<(), ExtensionBuildError> {
101        let def = extension.add_op(
102            self.opdef_id(),
103            self.description(),
104            self.init_signature(extension_ref),
105            extension_ref,
106        )?;
107
108        self.post_opdef(def);
109
110        Ok(())
111    }
112
113    /// Load all variants of an enum of op definitions in to an extension as op defs.
114    /// See [`strum::IntoEnumIterator`].
115    ///
116    /// Requires a [`Weak`] reference to the extension defining the operation.
117    /// This method is intended to be used inside the closure passed to [`Extension::new_arc`].
118    fn load_all_ops(
119        extension: &mut Extension,
120        extension_ref: &Weak<Extension>,
121    ) -> Result<(), ExtensionBuildError>
122    where
123        Self: IntoEnumIterator,
124    {
125        for op in Self::iter() {
126            op.add_to_extension(extension, extension_ref)?;
127        }
128        Ok(())
129    }
130
131    /// If the definition can be loaded from a string, load from an [`ExtensionOp`].
132    fn from_op(ext_op: &ExtensionOp) -> Result<Self, OpLoadError>
133    where
134        Self: Sized + std::str::FromStr,
135    {
136        Self::from_extension_op(ext_op)
137    }
138}
139
140/// [`MakeOpDef`] with an associate concrete Op type which can be instantiated with type arguments.
141pub trait HasConcrete: MakeOpDef {
142    /// Associated concrete type.
143    type Concrete: MakeExtensionOp;
144
145    /// Instantiate the operation with type arguments.
146    fn instantiate(&self, type_args: &[TypeArg]) -> Result<Self::Concrete, OpLoadError>;
147}
148
149/// [`MakeExtensionOp`] with an associated [`HasConcrete`].
150pub trait HasDef: MakeExtensionOp {
151    /// Associated [`HasConcrete`] type.
152    type Def: HasConcrete<Concrete = Self> + std::str::FromStr;
153
154    /// Load the operation from a [`ExtensionOp`].
155    fn from_op(ext_op: &ExtensionOp) -> Result<Self, OpLoadError>
156    where
157        Self: Sized,
158    {
159        Self::from_extension_op(ext_op)
160    }
161}
162
163/// Traits implemented by types which can be loaded from [`ExtensionOp`]s,
164/// i.e. concrete instances of [`OpDef`]s, with defined type arguments.
165pub trait MakeExtensionOp {
166    /// The [`OpDef::name`] of [`ExtensionOp`]s from which `Self` can be loaded.
167    ///
168    /// This identifier must be unique within the extension with which the
169    /// [`OpDef`] is registered. An [`ExtensionOp`] instantiating this [`OpDef`] will
170    /// report `self.opdef_id()` as its [`ExtensionOp::unqualified_id`].
171    fn op_id(&self) -> OpName;
172
173    /// Try to load one of the operations of this set from an [`OpDef`].
174    fn from_extension_op(ext_op: &ExtensionOp) -> Result<Self, OpLoadError>
175    where
176        Self: Sized;
177    /// Try to instantiate a variant from an [`OpType`]. Default behaviour assumes
178    /// an [`ExtensionOp`] and loads from the name.
179    #[must_use]
180    fn from_optype(op: &OpType) -> Option<Self>
181    where
182        Self: Sized,
183    {
184        let ext: &ExtensionOp = op.as_extension_op()?;
185        Self::from_extension_op(ext).ok()
186    }
187
188    /// Any type args which define this operation.
189    fn type_args(&self) -> Vec<TypeArg>;
190
191    /// Given the ID of the extension this operation is defined in, and a
192    /// registry containing that extension, return a [`RegisteredOp`].
193    fn to_registered(
194        self,
195        extension_id: ExtensionId,
196        extension: Arc<Extension>,
197    ) -> RegisteredOp<Self>
198    where
199        Self: Sized,
200    {
201        RegisteredOp {
202            extension_id,
203            extension,
204            op: self,
205        }
206    }
207}
208
209/// Blanket implementation for non-polymorphic operations - [`OpDef`]s with no type parameters.
210impl<T: MakeOpDef> MakeExtensionOp for T {
211    fn op_id(&self) -> OpName {
212        self.opdef_id()
213    }
214
215    #[inline]
216    fn from_extension_op(ext_op: &ExtensionOp) -> Result<Self, OpLoadError>
217    where
218        Self: Sized,
219    {
220        Self::from_def(ext_op.def())
221    }
222
223    #[inline]
224    fn type_args(&self) -> Vec<TypeArg> {
225        vec![]
226    }
227}
228
229/// Load an [`MakeOpDef`] from its name.
230/// See [`strum::EnumString`].
231pub fn try_from_name<T>(name: &OpNameRef, def_extension: &ExtensionId) -> Result<T, OpLoadError>
232where
233    T: std::str::FromStr + MakeOpDef,
234{
235    let op = T::from_str(name).map_err(|_| OpLoadError::NotMember(name.to_string()))?;
236    let expected_extension = op.extension();
237    if def_extension != &expected_extension {
238        return Err(OpLoadError::WrongExtension(
239            def_extension.clone(),
240            expected_extension,
241        ));
242    }
243
244    Ok(op)
245}
246
247/// Wrap an [`MakeExtensionOp`] with an extension registry to allow type computation.
248/// Generate from [`MakeExtensionOp::to_registered`]
249#[derive(Clone, Debug)]
250pub struct RegisteredOp<T> {
251    /// The name of the extension these ops belong to.
252    pub extension_id: ExtensionId,
253    /// A registry of all extensions, used for type computation.
254    extension: Arc<Extension>,
255    /// The inner [`MakeExtensionOp`]
256    op: T,
257}
258
259impl<T> RegisteredOp<T> {
260    /// Extract the inner wrapped value
261    pub fn to_inner(self) -> T {
262        self.op
263    }
264}
265
266impl<T: MakeExtensionOp> RegisteredOp<T> {
267    /// Generate an [`OpType`].
268    pub fn to_extension_op(&self) -> Result<ExtensionOp, SignatureError> {
269        let op_def = self.extension.get_op(&self.op_id()).unwrap_or_else(|| {
270            panic!(
271                "Extension::get_op() called with an invalid name ({}).",
272                self.op_id()
273            )
274        });
275        ExtensionOp::new(op_def.clone(), self.type_args())
276    }
277
278    delegate! {
279        to self.op {
280            /// Name of the operation - derived from strum serialization.
281            pub fn op_id(&self) -> OpName;
282            /// Any type args which define this operation. Default is no type arguments.
283            pub fn type_args(&self) -> Vec<TypeArg>;
284        }
285    }
286}
287
288/// Trait for operations that can self report the extension ID they belong to
289/// and the registry required to compute their types.
290/// Allows conversion to [`ExtensionOp`]
291pub trait MakeRegisteredOp: MakeExtensionOp {
292    /// The ID of the extension this op belongs to.
293    fn extension_id(&self) -> ExtensionId;
294    /// A reference to the [Extension] which defines this operation.
295    fn extension_ref(&self) -> Arc<Extension>;
296
297    /// Convert this operation in to an [`ExtensionOp`]. Returns None if the type
298    /// cannot be computed.
299    fn to_extension_op(self) -> Result<ExtensionOp, SignatureError>
300    where
301        Self: Sized,
302    {
303        let registered: RegisteredOp<_> = self.into();
304        registered.to_extension_op()
305    }
306}
307
308impl<T: MakeRegisteredOp> From<T> for RegisteredOp<T> {
309    fn from(ext_op: T) -> Self {
310        let extension_id = ext_op.extension_id();
311        let extension = ext_op.extension_ref();
312        ext_op.to_registered(extension_id, extension)
313    }
314}
315
316impl<T: MakeRegisteredOp> From<T> for OpType {
317    /// Convert
318    fn from(ext_op: T) -> Self {
319        ext_op.to_extension_op().unwrap().into()
320    }
321}
322
323#[cfg(test)]
324mod test {
325    use std::sync::{Arc, LazyLock};
326
327    use crate::{
328        const_extension_ids, type_row,
329        types::{Signature, Term},
330    };
331
332    use super::*;
333    use strum::{EnumIter, EnumString, IntoStaticStr};
334
335    #[derive(Clone, Debug, Hash, PartialEq, Eq, EnumIter, IntoStaticStr, EnumString)]
336    enum DummyEnum {
337        Dumb,
338    }
339
340    impl MakeOpDef for DummyEnum {
341        fn opdef_id(&self) -> OpName {
342            <&'static str>::from(self).into()
343        }
344
345        fn init_signature(&self, _extension_ref: &Weak<Extension>) -> SignatureFunc {
346            Signature::new_endo(type_row![]).into()
347        }
348
349        fn extension_ref(&self) -> Weak<Extension> {
350            Arc::downgrade(&EXT)
351        }
352
353        fn from_def(_op_def: &OpDef) -> Result<Self, OpLoadError> {
354            Ok(Self::Dumb)
355        }
356
357        fn extension(&self) -> ExtensionId {
358            EXT_ID.clone()
359        }
360    }
361
362    impl HasConcrete for DummyEnum {
363        type Concrete = Self;
364
365        fn instantiate(&self, _type_args: &[TypeArg]) -> Result<Self::Concrete, OpLoadError> {
366            if _type_args.is_empty() {
367                Ok(self.clone())
368            } else {
369                Err(OpLoadError::InvalidArgs(SignatureError::InvalidTypeArgs))
370            }
371        }
372    }
373    const_extension_ids! {
374        const EXT_ID: ExtensionId = "DummyExt";
375    }
376
377    static EXT: LazyLock<Arc<Extension>> = LazyLock::new(|| {
378        Extension::new_test_arc(EXT_ID.clone(), |ext, extension_ref| {
379            DummyEnum::Dumb
380                .add_to_extension(ext, extension_ref)
381                .unwrap();
382        })
383    });
384
385    impl MakeRegisteredOp for DummyEnum {
386        fn extension_id(&self) -> ExtensionId {
387            EXT_ID.clone()
388        }
389
390        fn extension_ref(&self) -> Arc<Extension> {
391            EXT.clone()
392        }
393    }
394
395    #[test]
396    fn test_dummy_enum() {
397        let o = DummyEnum::Dumb;
398
399        assert_eq!(
400            DummyEnum::from_def(EXT.get_op(&o.opdef_id()).unwrap()).unwrap(),
401            o
402        );
403
404        assert_eq!(
405            DummyEnum::from_optype(&o.clone().to_extension_op().unwrap().into()).unwrap(),
406            o
407        );
408        assert_eq!(format!("{EXT_ID}.Dumb"), o.qualified_opdef_id());
409        let registered: RegisteredOp<_> = o.clone().into();
410        assert_eq!(registered.to_inner(), o);
411
412        assert_eq!(o.instantiate(&[]), Ok(o.clone()));
413        assert_eq!(
414            o.instantiate(&[Term::from(1u64)]),
415            Err(OpLoadError::InvalidArgs(SignatureError::InvalidTypeArgs))
416        );
417    }
418}