hugr_core/
import.rs

1//! Importing HUGR graphs from their `hugr-model` representation.
2//!
3//! **Warning**: This module is still under development and is expected to change.
4//! It is included in the library to allow for early experimentation, and for
5//! the core and model to converge incrementally.
6use std::sync::Arc;
7
8use crate::{
9    Direction, Hugr, HugrView, Node, Port,
10    envelope::{
11        GENERATOR_KEY, USED_EXTENSIONS_KEY,
12        description::{ExtensionDesc, ModuleDesc},
13    },
14    extension::{
15        ExtensionId, ExtensionRegistry, SignatureError, resolution::ExtensionResolutionError,
16    },
17    hugr::{HugrMut, NodeMetadata},
18    ops::{
19        AliasDecl, AliasDefn, CFG, Call, CallIndirect, Case, Conditional, Const, DFG,
20        DataflowBlock, ExitBlock, FuncDecl, FuncDefn, Input, LoadConstant, LoadFunction, OpType,
21        OpaqueOp, Output, Tag, TailLoop, Value,
22        constant::{CustomConst, CustomSerialized, OpaqueValue},
23    },
24    package::Package,
25    std_extensions::{
26        arithmetic::{float_types::ConstF64, int_types::ConstInt},
27        collections::array::ArrayValue,
28    },
29    types::{
30        CustomType, FuncTypeBase, MaybeRV, PolyFuncType, PolyFuncTypeBase, RowVariable, Signature,
31        Term, Type, TypeArg, TypeBase, TypeBound, TypeEnum, TypeName, TypeRow,
32        type_param::{SeqPart, TypeParam},
33        type_row::TypeRowBase,
34    },
35};
36use hugr_model::v0::table;
37use hugr_model::v0::{self as model};
38use itertools::{Either, Itertools};
39use rustc_hash::FxHashMap;
40use smol_str::{SmolStr, ToSmolStr};
41use thiserror::Error;
42
43/// An error that can occur during import.
44#[derive(Debug, Clone, Error)]
45#[error("failed to import hugr")]
46pub struct ImportError {
47    #[source]
48    inner: ImportErrorInner,
49}
50
51const UNSUPPORTED_HINT: &str = concat!(
52    "Hint: The import process encountered a `hugr-model` feature ",
53    "that is currently unsupported in `hugr-core`. ",
54    "As `hugr-core` evolves towards the same expressivity as `hugr-model` ",
55    "we expect that errors of this kind will be removed.",
56);
57
58const UNINFERRED_HINT: &str = concat!(
59    "Hint: The import process encountered implicit information in the `hugr-model` ",
60    "package that it can not yet fill in. ",
61    "Such implicit information can be signatures for nodes and regions, ",
62    "wildcard terms or symbol applications with fewer arguments than the symbol has parameters. ",
63    "Until more comprehensive inference is implemented, `hugr-model` packages will need to be very explicit. ",
64    "To avoid this error, make sure to include as much information as possible when generating packages."
65);
66
67/// Error hint explaining the concept of a closed tuple.
68const CLOSED_TUPLE_HINT: &str = concat!(
69    "Hint: A tuple is closed if all of its items are known. ",
70    "Closed tuples can contain spliced subtuples, as long as they can be recursively flattened ",
71    "into a tuple that only contains individual items."
72);
73
74/// Error hint explaining the concept of a closed list.
75const CLOSED_LIST_HINT: &str = concat!(
76    "Hint: A list is closed if all of its items are known. ",
77    "Closed lists can contain spliced sublists, as long as they can be recursively flattened ",
78    "into a list that only contains individual items."
79);
80
81#[derive(Debug, Clone, Error)]
82enum ImportErrorInner {
83    /// The model contains a feature that is not supported by the importer yet.
84    /// Errors of this kind are expected to be removed as the model format and
85    /// the core HUGR representation converge.
86    #[error("Unsupported: {0}\n{hint}", hint = UNSUPPORTED_HINT)]
87    Unsupported(String),
88
89    /// The model contains implicit information that has not yet been inferred.
90    /// This includes wildcards and application of functions with implicit parameters.
91    #[error("Uninferred implicit: {0}\n{hint}", hint = UNINFERRED_HINT)]
92    Uninferred(String),
93
94    /// The model is not well-formed.
95    #[error("{0}")]
96    Invalid(String),
97
98    /// An error with additional context.
99    #[error("import failed in context: {1}")]
100    Context(#[source] Box<ImportErrorInner>, String),
101
102    /// A signature mismatch was detected during import.
103    #[error("signature error")]
104    Signature(#[from] SignatureError),
105
106    /// An error relating to the loaded extension registry.
107    #[error("extension error")]
108    Extension(#[from] ExtensionError),
109
110    /// Incorrect order hints.
111    #[error("incorrect order hint")]
112    OrderHint(#[from] OrderHintError),
113
114    /// Extension resolution.
115    #[error("extension resolution error")]
116    ExtensionResolution(#[from] ExtensionResolutionError),
117}
118
119#[derive(Debug, Clone, Error)]
120enum ExtensionError {
121    /// An extension is missing.
122    #[error("Importing the hugr requires extension {missing_ext}, which was not found in the registry. The available extensions are: [{}]",
123            available.iter().map(std::string::ToString::to_string).collect::<Vec<_>>().join(", "))]
124    Missing {
125        /// The missing extension.
126        missing_ext: ExtensionId,
127        /// The available extensions in the registry.
128        available: Vec<ExtensionId>,
129    },
130
131    /// An extension type is missing.
132    #[error(
133        "Importing the hugr requires extension {ext} to have a type named {name}, but it was not found."
134    )]
135    MissingType {
136        /// The extension that is missing the type.
137        ext: ExtensionId,
138        /// The name of the missing type.
139        name: TypeName,
140    },
141}
142
143/// Import error caused by incorrect order hints.
144#[derive(Debug, Clone, Error)]
145enum OrderHintError {
146    /// Duplicate order hint key in the same region.
147    #[error("duplicate order hint key {0}")]
148    DuplicateKey(table::RegionId, u64),
149    /// Order hint including a key not defined in the region.
150    #[error("order hint with unknown key {0}")]
151    UnknownKey(u64),
152    /// Order hint involving a node with no order port.
153    #[error("order hint on node with no order port: {0}")]
154    NoOrderPort(table::NodeId),
155}
156
157/// Helper macro to create an `ImportErrorInner::Unsupported` error with a formatted message.
158macro_rules! error_unsupported {
159    ($($e:expr),*) => { ImportErrorInner::Unsupported(format!($($e),*)) }
160}
161
162/// Helper macro to create an `ImportErrorInner::Uninferred` error with a formatted message.
163macro_rules! error_uninferred {
164    ($($e:expr),*) => { ImportErrorInner::Uninferred(format!($($e),*)) }
165}
166
167/// Helper macro to create an `ImportErrorInner::Invalid` error with a formatted message.
168macro_rules! error_invalid {
169    ($($e:expr),*) => { ImportErrorInner::Invalid(format!($($e),*)) }
170}
171
172/// Helper macro to create an `ImportErrorInner::Context` error with a formatted message.
173macro_rules! error_context {
174    ($err:expr, $($e:expr),*) => {
175        {
176            ImportErrorInner::Context(Box::new($err), format!($($e),*))
177        }
178    }
179}
180
181/// Import a [`Package`] from the model representation
182/// of the modules and any included extensions.
183pub fn import_package(
184    package: &table::Package,
185    packaged_extensions: ExtensionRegistry,
186    loaded_extensions: &ExtensionRegistry,
187) -> Result<Package, ImportError> {
188    let modules = package
189        .modules
190        .iter()
191        .map(|module| import_hugr(module, loaded_extensions))
192        .collect::<Result<Vec<_>, _>>()?;
193
194    // This does not panic since the import already requires a module root.
195    let mut package = Package::new(modules);
196    package.extensions = packaged_extensions;
197    Ok(package)
198}
199
200/// Get the name of the generator from the metadata of the module.
201/// If no generator is found, `None` is returned.
202fn get_generator(ctx: &Context<'_>) -> Option<String> {
203    ctx.module
204        .get_region(ctx.module.root)
205        .map(|r| r.meta.iter())
206        .into_iter()
207        .flatten()
208        .find_map(|meta| {
209            let (name, json_val) = ctx.decode_json_meta(*meta).ok()??;
210
211            (name == GENERATOR_KEY).then_some(crate::envelope::format_generator(&json_val))
212        })
213}
214
215fn get_used_exts(ctx: &Context<'_>) -> Option<Vec<ExtensionDesc>> {
216    ctx.module
217        .get_region(ctx.module.root)
218        .map(|r| r.meta.iter())
219        .into_iter()
220        .flatten()
221        .find_map(|meta| {
222            let (name, json_val) = ctx.decode_json_meta(*meta).ok()??;
223
224            (name == USED_EXTENSIONS_KEY)
225                .then(|| serde_json::from_value(json_val).ok())
226                .flatten()
227        })
228}
229
230/// Import a [`Hugr`] module from its model representation.
231pub fn import_hugr(
232    module: &table::Module,
233    extensions: &ExtensionRegistry,
234) -> Result<Hugr, ImportError> {
235    let (_, res) = import_described_hugr(module, extensions);
236    res
237}
238
239pub(crate) fn import_described_hugr(
240    module: &table::Module,
241    extensions: &ExtensionRegistry,
242) -> (ModuleDesc, Result<Hugr, ImportError>) {
243    // TODO: Module should know about the number of edges, so that we can use a vector here.
244    // For now we use a hashmap, which will be slower.
245    let mut ctx = Context {
246        module,
247        hugr: Hugr::new(),
248        link_ports: FxHashMap::default(),
249        static_edges: Vec::new(),
250        extensions,
251        nodes: FxHashMap::default(),
252        local_vars: FxHashMap::default(),
253        custom_name_cache: FxHashMap::default(),
254        region_scope: table::RegionId::default(),
255        description: ModuleDesc::default(),
256    };
257
258    if let Some(s) = get_generator(&ctx) {
259        ctx.description.set_generator(s);
260    }
261    if let Some(exts) = get_used_exts(&ctx) {
262        ctx.description.set_used_extensions_generator(exts);
263    }
264    ctx.description.set_num_nodes(module.nodes.len());
265    let import_steps: [fn(&mut Context) -> _; 3] = [
266        |ctx| ctx.import_root(),
267        |ctx| ctx.link_ports(),
268        |ctx| ctx.link_static_ports(),
269    ];
270
271    for step in import_steps {
272        if let Err(e) = step(&mut ctx) {
273            return (ctx.description, Err(ImportError { inner: e }));
274        }
275    }
276    (ctx.description, Ok(ctx.hugr))
277}
278
279struct Context<'a> {
280    /// The module being imported.
281    module: &'a table::Module<'a>,
282
283    /// The HUGR graph being constructed.
284    hugr: Hugr,
285
286    /// The ports that are part of each link. This is used to connect the ports at the end of the
287    /// import process.
288    link_ports: FxHashMap<(table::RegionId, table::LinkIndex), Vec<(Node, Port)>>,
289
290    /// Pairs of nodes that should be connected by a static edge.
291    /// These are collected during the import process and connected at the end.
292    static_edges: Vec<(table::NodeId, table::NodeId)>,
293
294    /// The ambient extension registry to use for importing.
295    extensions: &'a ExtensionRegistry,
296
297    /// A map from `NodeId` to the imported `Node`.
298    nodes: FxHashMap<table::NodeId, Node>,
299
300    local_vars: FxHashMap<table::VarId, LocalVar>,
301
302    custom_name_cache: FxHashMap<&'a str, (ExtensionId, SmolStr)>,
303
304    region_scope: table::RegionId,
305
306    description: ModuleDesc,
307}
308
309impl<'a> Context<'a> {
310    /// Get the signature of the node with the given `NodeId`.
311    fn get_node_signature(&mut self, node: table::NodeId) -> Result<Signature, ImportErrorInner> {
312        let node_data = self.get_node(node)?;
313        let signature = node_data
314            .signature
315            .ok_or_else(|| error_uninferred!("node signature"))?;
316        self.import_func_type(signature)
317    }
318
319    /// Get the node with the given `NodeId`, or return an error if it does not exist.
320    #[inline]
321    fn get_node(&self, node_id: table::NodeId) -> Result<&'a table::Node<'a>, ImportErrorInner> {
322        self.module
323            .get_node(node_id)
324            .ok_or_else(|| error_invalid!("unknown node {}", node_id))
325    }
326
327    /// Get the term with the given `TermId`, or return an error if it does not exist.
328    #[inline]
329    fn get_term(&self, term_id: table::TermId) -> Result<&'a table::Term<'a>, ImportErrorInner> {
330        self.module
331            .get_term(term_id)
332            .ok_or_else(|| error_invalid!("unknown term {}", term_id))
333    }
334
335    /// Get the region with the given `RegionId`, or return an error if it does not exist.
336    #[inline]
337    fn get_region(
338        &self,
339        region_id: table::RegionId,
340    ) -> Result<&'a table::Region<'a>, ImportErrorInner> {
341        self.module
342            .get_region(region_id)
343            .ok_or_else(|| error_invalid!("unknown region {}", region_id))
344    }
345
346    fn make_node(
347        &mut self,
348        node_id: table::NodeId,
349        op: OpType,
350        parent: Node,
351    ) -> Result<Node, ImportErrorInner> {
352        let node = self.hugr.add_node_with_parent(parent, op);
353        self.nodes.insert(node_id, node);
354
355        let node_data = self.get_node(node_id)?;
356        self.record_links(node, Direction::Incoming, node_data.inputs);
357        self.record_links(node, Direction::Outgoing, node_data.outputs);
358
359        for meta_item in node_data.meta {
360            self.import_node_metadata(node, *meta_item)
361                .map_err(|err| error_context!(err, "node metadata"))?;
362        }
363
364        Ok(node)
365    }
366
367    fn import_node_metadata(
368        &mut self,
369        node: Node,
370        meta_item: table::TermId,
371    ) -> Result<(), ImportErrorInner> {
372        // Import the JSON metadata
373        if let Some((name, json_value)) = self.decode_json_meta(meta_item)? {
374            self.hugr.set_metadata(node, name, json_value);
375        }
376
377        // Set the entrypoint
378        if let Some([]) = self.match_symbol(meta_item, model::CORE_ENTRYPOINT)? {
379            self.hugr.set_entrypoint(node);
380            self.description.load_entrypoint(&self.hugr);
381        }
382
383        Ok(())
384    }
385
386    fn decode_json_meta(
387        &self,
388        meta_item: table::TermId,
389    ) -> Result<Option<(SmolStr, serde_json::Value)>, ImportErrorInner> {
390        Ok(
391            if let Some([name_arg, json_arg]) =
392                self.match_symbol(meta_item, model::COMPAT_META_JSON)?
393            {
394                let table::Term::Literal(model::Literal::Str(name)) = self.get_term(name_arg)?
395                else {
396                    return Err(error_invalid!(
397                        "`{}` expects a string literal as its first argument",
398                        model::COMPAT_META_JSON
399                    ));
400                };
401
402                let table::Term::Literal(model::Literal::Str(json_str)) =
403                    self.get_term(json_arg)?
404                else {
405                    return Err(error_invalid!(
406                        "`{}` expects a string literal as its second argument",
407                        model::COMPAT_CONST_JSON
408                    ));
409                };
410
411                let json_value: NodeMetadata = serde_json::from_str(json_str).map_err(|_| {
412                    error_invalid!(
413                        "failed to parse JSON string for `{}` metadata",
414                        model::COMPAT_CONST_JSON
415                    )
416                })?;
417                Some((name.to_owned(), json_value))
418            } else {
419                None
420            },
421        )
422    }
423
424    /// Associate links with the ports of the given node in the given direction.
425    fn record_links(&mut self, node: Node, direction: Direction, links: &'a [table::LinkIndex]) {
426        let optype = self.hugr.get_optype(node);
427        // NOTE: `OpType::port_count` copies the signature, which significantly slows down the import.
428        debug_assert!(links.len() <= optype.port_count(direction));
429
430        for (link, port) in links.iter().zip(self.hugr.node_ports(node, direction)) {
431            self.link_ports
432                .entry((self.region_scope, *link))
433                .or_default()
434                .push((node, port));
435        }
436    }
437
438    /// Link up the ports in the hugr graph, according to the connectivity information that
439    /// has been gathered in the `link_ports` map.
440    fn link_ports(&mut self) -> Result<(), ImportErrorInner> {
441        // For each edge, we group the ports by their direction. We reuse the `inputs` and
442        // `outputs` vectors to avoid unnecessary allocations.
443        let mut inputs = Vec::new();
444        let mut outputs = Vec::new();
445
446        for (link_id, link_ports) in std::mem::take(&mut self.link_ports) {
447            // Skip the edge if it doesn't have any ports.
448            if link_ports.is_empty() {
449                continue;
450            }
451
452            for (node, port) in link_ports {
453                match port.as_directed() {
454                    Either::Left(input) => inputs.push((node, input)),
455                    Either::Right(output) => outputs.push((node, output)),
456                }
457            }
458
459            match (inputs.as_slice(), outputs.as_slice()) {
460                ([], []) => {
461                    unreachable!();
462                }
463                (_, [output]) => {
464                    for (node, port) in &inputs {
465                        self.hugr.connect(output.0, output.1, *node, *port);
466                    }
467                }
468                ([input], _) => {
469                    for (node, port) in &outputs {
470                        self.hugr.connect(*node, *port, input.0, input.1);
471                    }
472                }
473                _ => {
474                    return Err(error_unsupported!(
475                        concat!(
476                            "Link {:?} would require a hyperedge because it connects more ",
477                            "than one input port with more than one output port."
478                        ),
479                        link_id
480                    ));
481                }
482            }
483
484            inputs.clear();
485            outputs.clear();
486        }
487
488        Ok(())
489    }
490
491    /// Connects static ports according to the connections in [`self.static_edges`].
492    fn link_static_ports(&mut self) -> Result<(), ImportErrorInner> {
493        for (src_id, dst_id) in std::mem::take(&mut self.static_edges) {
494            // None of these lookups should fail given how we constructed `static_edges`.
495            let src = self.nodes[&src_id];
496            let dst = self.nodes[&dst_id];
497            let src_port = self.hugr.get_optype(src).static_output_port().unwrap();
498            let dst_port = self.hugr.get_optype(dst).static_input_port().unwrap();
499            self.hugr.connect(src, src_port, dst, dst_port);
500        }
501
502        Ok(())
503    }
504
505    fn get_symbol_name(&self, node_id: table::NodeId) -> Result<&'a str, ImportErrorInner> {
506        let node_data = self.get_node(node_id)?;
507        let name = node_data
508            .operation
509            .symbol()
510            .ok_or_else(|| error_invalid!("node {} is expected to be a symbol", node_id))?;
511        Ok(name)
512    }
513
514    fn get_func_signature(
515        &mut self,
516        func_node: table::NodeId,
517    ) -> Result<PolyFuncType, ImportErrorInner> {
518        let symbol = match self.get_node(func_node)?.operation {
519            table::Operation::DefineFunc(symbol) => symbol,
520            table::Operation::DeclareFunc(symbol) => symbol,
521            _ => {
522                return Err(error_invalid!(
523                    "node {} is expected to be a function declaration or definition",
524                    func_node
525                ));
526            }
527        };
528
529        self.import_poly_func_type(func_node, *symbol, |_, signature| Ok(signature))
530    }
531
532    /// Import the root region of the module.
533    fn import_root(&mut self) -> Result<(), ImportErrorInner> {
534        self.region_scope = self.module.root;
535        let region_data = self.get_region(self.module.root)?;
536
537        for node in region_data.children {
538            self.import_node(*node, self.hugr.module_root())?;
539        }
540
541        for meta_item in region_data.meta {
542            self.import_node_metadata(self.hugr.module_root(), *meta_item)?;
543        }
544
545        Ok(())
546    }
547
548    fn import_node(
549        &mut self,
550        node_id: table::NodeId,
551        parent: Node,
552    ) -> Result<Option<Node>, ImportErrorInner> {
553        let node_data = self.get_node(node_id)?;
554
555        let result = match node_data.operation {
556            table::Operation::Invalid => {
557                return Err(error_invalid!(concat!(
558                    "Tried to import an `invalid` operation.\n",
559                    "The `invalid` operation in `hugr-model` is a placeholder indicating a missing operation. ",
560                    "It currently has no equivalent in `hugr-core`."
561                )));
562            }
563
564            table::Operation::Dfg => Some(
565                self.import_node_dfg(node_id, parent, node_data)
566                    .map_err(|err| error_context!(err, "`dfg` node with id {}", node_id))?,
567            ),
568
569            table::Operation::Cfg => Some(
570                self.import_node_cfg(node_id, parent, node_data)
571                    .map_err(|err| error_context!(err, "`cfg` node with id {}", node_id))?,
572            ),
573
574            table::Operation::Block => Some(
575                self.import_node_block(node_id, parent)
576                    .map_err(|err| error_context!(err, "`block` node with id {}", node_id))?,
577            ),
578
579            table::Operation::DefineFunc(symbol) => Some(
580                self.import_node_define_func(node_id, symbol, node_data, parent)
581                    .map_err(|err| error_context!(err, "`define-func` node with id {}", node_id))?,
582            ),
583
584            table::Operation::DeclareFunc(symbol) => Some(
585                self.import_node_declare_func(node_id, symbol, parent)
586                    .map_err(|err| {
587                        error_context!(err, "`declare-func` node with id {}", node_id)
588                    })?,
589            ),
590
591            table::Operation::TailLoop => Some(
592                self.import_tail_loop(node_id, parent)
593                    .map_err(|err| error_context!(err, "`tail-loop` node with id {}", node_id))?,
594            ),
595
596            table::Operation::Conditional => Some(
597                self.import_conditional(node_id, parent)
598                    .map_err(|err| error_context!(err, "`cond` node with id {}", node_id))?,
599            ),
600
601            table::Operation::Custom(operation) => Some(
602                self.import_node_custom(node_id, operation, node_data, parent)
603                    .map_err(|err| error_context!(err, "custom node with id {}", node_id))?,
604            ),
605
606            table::Operation::DefineAlias(symbol, value) => Some(
607                self.import_node_define_alias(node_id, symbol, value, parent)
608                    .map_err(|err| {
609                        error_context!(err, "`define-alias` node with id {}", node_id)
610                    })?,
611            ),
612
613            table::Operation::DeclareAlias(symbol) => Some(
614                self.import_node_declare_alias(node_id, symbol, parent)
615                    .map_err(|err| {
616                        error_context!(err, "`declare-alias` node with id {}", node_id)
617                    })?,
618            ),
619
620            table::Operation::Import { .. } => None,
621
622            table::Operation::DeclareConstructor { .. } => None,
623            table::Operation::DeclareOperation { .. } => None,
624        };
625
626        Ok(result)
627    }
628
629    fn import_node_dfg(
630        &mut self,
631        node_id: table::NodeId,
632        parent: Node,
633        node_data: &'a table::Node<'a>,
634    ) -> Result<Node, ImportErrorInner> {
635        let signature = self
636            .get_node_signature(node_id)
637            .map_err(|err| error_context!(err, "node signature"))?;
638
639        let optype = OpType::DFG(DFG { signature });
640        let node = self.make_node(node_id, optype, parent)?;
641
642        let [region] = node_data.regions else {
643            return Err(error_invalid!("dfg region expects a single region"));
644        };
645
646        self.import_dfg_region(*region, node)?;
647        Ok(node)
648    }
649
650    fn import_node_cfg(
651        &mut self,
652        node_id: table::NodeId,
653        parent: Node,
654        node_data: &'a table::Node<'a>,
655    ) -> Result<Node, ImportErrorInner> {
656        let signature = self
657            .get_node_signature(node_id)
658            .map_err(|err| error_context!(err, "node signature"))?;
659
660        let optype = OpType::CFG(CFG { signature });
661        let node = self.make_node(node_id, optype, parent)?;
662
663        let [region] = node_data.regions else {
664            return Err(error_invalid!("cfg nodes expect a single region"));
665        };
666
667        self.import_cfg_region(*region, node)?;
668        Ok(node)
669    }
670
671    fn import_dfg_region(
672        &mut self,
673        region: table::RegionId,
674        node: Node,
675    ) -> Result<(), ImportErrorInner> {
676        let region_data = self.get_region(region)?;
677
678        let prev_region = self.region_scope;
679        if region_data.scope.is_some() {
680            self.region_scope = region;
681        }
682
683        if region_data.kind != model::RegionKind::DataFlow {
684            return Err(error_invalid!("expected dfg region"));
685        }
686
687        let signature = self
688            .import_func_type(
689                region_data
690                    .signature
691                    .ok_or_else(|| error_uninferred!("region signature"))?,
692            )
693            .map_err(|err| error_context!(err, "signature of dfg region with id {}", region))?;
694
695        // Create the input and output nodes
696        let input = self.hugr.add_node_with_parent(
697            node,
698            OpType::Input(Input {
699                types: signature.input,
700            }),
701        );
702        let output = self.hugr.add_node_with_parent(
703            node,
704            OpType::Output(Output {
705                types: signature.output,
706            }),
707        );
708
709        // Make sure that the ports of the input/output nodes are connected correctly
710        self.record_links(input, Direction::Outgoing, region_data.sources);
711        self.record_links(output, Direction::Incoming, region_data.targets);
712
713        for child in region_data.children {
714            self.import_node(*child, node)?;
715        }
716
717        self.create_order_edges(region, input, output)?;
718
719        for meta_item in region_data.meta {
720            self.import_node_metadata(node, *meta_item)?;
721        }
722
723        self.region_scope = prev_region;
724
725        Ok(())
726    }
727
728    /// Create order edges between nodes of a dataflow region based on order hint metadata.
729    ///
730    /// This method assumes that the nodes for the children of the region have already been imported.
731    fn create_order_edges(
732        &mut self,
733        region_id: table::RegionId,
734        input: Node,
735        output: Node,
736    ) -> Result<(), ImportErrorInner> {
737        let region_data = self.get_region(region_id)?;
738        debug_assert_eq!(region_data.kind, model::RegionKind::DataFlow);
739
740        // Collect order hint keys
741        // PERFORMANCE: It might be worthwhile to reuse the map to avoid allocations.
742        let mut order_keys = FxHashMap::<u64, Node>::default();
743
744        for child_id in region_data.children {
745            let child_data = self.get_node(*child_id)?;
746
747            for meta_id in child_data.meta {
748                let Some([key]) = self.match_symbol(*meta_id, model::ORDER_HINT_KEY)? else {
749                    continue;
750                };
751
752                let table::Term::Literal(model::Literal::Nat(key)) = self.get_term(key)? else {
753                    continue;
754                };
755
756                // NOTE: The lookups here are expected to succeed since we only
757                // process the order metadata after we have imported the nodes.
758                let child_node = self.nodes[child_id];
759                let child_optype = self.hugr.get_optype(child_node);
760
761                // Check that the node has order ports.
762                // NOTE: This assumes that a node has an input order port iff it has an output one.
763                if child_optype.other_output_port().is_none() {
764                    return Err(OrderHintError::NoOrderPort(*child_id).into());
765                }
766
767                if order_keys.insert(*key, child_node).is_some() {
768                    return Err(OrderHintError::DuplicateKey(region_id, *key).into());
769                }
770            }
771        }
772
773        // Collect the order hint keys for the input and output nodes
774        for meta_id in region_data.meta {
775            if let Some([key]) = self.match_symbol(*meta_id, model::ORDER_HINT_INPUT_KEY)? {
776                let table::Term::Literal(model::Literal::Nat(key)) = self.get_term(key)? else {
777                    continue;
778                };
779
780                if order_keys.insert(*key, input).is_some() {
781                    return Err(OrderHintError::DuplicateKey(region_id, *key).into());
782                }
783            }
784
785            if let Some([key]) = self.match_symbol(*meta_id, model::ORDER_HINT_OUTPUT_KEY)? {
786                let table::Term::Literal(model::Literal::Nat(key)) = self.get_term(key)? else {
787                    continue;
788                };
789
790                if order_keys.insert(*key, output).is_some() {
791                    return Err(OrderHintError::DuplicateKey(region_id, *key).into());
792                }
793            }
794        }
795
796        // Insert order edges
797        for meta_id in region_data.meta {
798            let Some([a, b]) = self.match_symbol(*meta_id, model::ORDER_HINT_ORDER)? else {
799                continue;
800            };
801
802            let table::Term::Literal(model::Literal::Nat(a)) = self.get_term(a)? else {
803                continue;
804            };
805
806            let table::Term::Literal(model::Literal::Nat(b)) = self.get_term(b)? else {
807                continue;
808            };
809
810            let a = order_keys.get(a).ok_or(OrderHintError::UnknownKey(*a))?;
811            let b = order_keys.get(b).ok_or(OrderHintError::UnknownKey(*b))?;
812
813            // NOTE: The unwrap here must succeed:
814            // - For all ordinary nodes we checked that they have an order port.
815            // - Input and output nodes always have an order port.
816            let a_port = self.hugr.get_optype(*a).other_output_port().unwrap();
817            let b_port = self.hugr.get_optype(*b).other_input_port().unwrap();
818
819            self.hugr.connect(*a, a_port, *b, b_port);
820        }
821
822        Ok(())
823    }
824
825    /// Imports a closed list whose first element is an ADT with a closed list
826    /// of variants. The variants and the remaining elements of the list are
827    /// returned as type rows.
828    fn import_adt_and_rest(
829        &mut self,
830        list: table::TermId,
831    ) -> Result<(Vec<TypeRow>, TypeRow), ImportErrorInner> {
832        let items = self.import_closed_list(list)?;
833
834        let Some((first, rest)) = items.split_first() else {
835            return Err(error_invalid!("expected list to have at least one element"));
836        };
837
838        let sum_rows: Vec<_> = {
839            let [variants] = self.expect_symbol(*first, model::CORE_ADT)?;
840            self.import_type_rows(variants)?
841        };
842
843        let rest = rest
844            .iter()
845            .map(|term| self.import_type(*term))
846            .collect::<Result<Vec<_>, _>>()?
847            .into();
848
849        Ok((sum_rows, rest))
850    }
851
852    fn import_tail_loop(
853        &mut self,
854        node_id: table::NodeId,
855        parent: Node,
856    ) -> Result<Node, ImportErrorInner> {
857        let node_data = self.get_node(node_id)?;
858        debug_assert_eq!(node_data.operation, table::Operation::TailLoop);
859
860        let [region] = node_data.regions else {
861            return Err(error_invalid!(
862                "loop node {} expects a single region",
863                node_id
864            ));
865        };
866
867        let region_data = self.get_region(*region)?;
868
869        let (just_inputs, just_outputs, rest) = (|| {
870            let [_, region_outputs] = self.get_func_type(
871                region_data
872                    .signature
873                    .ok_or_else(|| error_uninferred!("region signature"))?,
874            )?;
875            let (sum_rows, rest) = self.import_adt_and_rest(region_outputs)?;
876
877            if sum_rows.len() != 2 {
878                return Err(error_invalid!(
879                    "Loop nodes expect their first target to be an ADT with two variants."
880                ));
881            }
882
883            let mut sum_rows = sum_rows.into_iter();
884            let just_inputs = sum_rows.next().unwrap();
885            let just_outputs = sum_rows.next().unwrap();
886
887            Ok((just_inputs, just_outputs, rest))
888        })()
889        .map_err(|err| error_context!(err, "region signature"))?;
890
891        let optype = OpType::TailLoop(TailLoop {
892            just_inputs,
893            just_outputs,
894            rest,
895        });
896
897        let node = self.make_node(node_id, optype, parent)?;
898
899        self.import_dfg_region(*region, node)?;
900        Ok(node)
901    }
902
903    fn import_conditional(
904        &mut self,
905        node_id: table::NodeId,
906        parent: Node,
907    ) -> Result<Node, ImportErrorInner> {
908        let node_data = self.get_node(node_id)?;
909        debug_assert_eq!(node_data.operation, table::Operation::Conditional);
910
911        let (sum_rows, other_inputs, outputs) = (|| {
912            let [inputs, outputs] = self.get_func_type(
913                node_data
914                    .signature
915                    .ok_or_else(|| error_uninferred!("node signature"))?,
916            )?;
917            let (sum_rows, other_inputs) = self.import_adt_and_rest(inputs)?;
918            let outputs = self.import_type_row(outputs)?;
919
920            Ok((sum_rows, other_inputs, outputs))
921        })()
922        .map_err(|err| error_context!(err, "node signature"))?;
923
924        let optype = OpType::Conditional(Conditional {
925            sum_rows,
926            other_inputs,
927            outputs,
928        });
929
930        let node = self.make_node(node_id, optype, parent)?;
931
932        for region in node_data.regions {
933            let region_data = self.get_region(*region)?;
934            let signature = self.import_func_type(
935                region_data
936                    .signature
937                    .ok_or_else(|| error_uninferred!("region signature"))?,
938            )?;
939
940            let case_node = self
941                .hugr
942                .add_node_with_parent(node, OpType::Case(Case { signature }));
943
944            self.import_dfg_region(*region, case_node)?;
945        }
946
947        Ok(node)
948    }
949
950    /// Imports a control flow region.
951    ///
952    /// The `hugr-model` and `hugr-core` representations of control flow are
953    /// slightly different, and so this method needs to perform some conversion.
954    ///
955    /// In `hugr-core` the first node in the region is a [`BasicBlock`] that by
956    /// virtue of its position is the designated entry block. The second node in
957    /// the region is an [`ExitBlock`]. The [`ExitBlock`] is analogous to an
958    /// [`Output`] node in a dataflow graph, while there is no direct control flow
959    /// equivalent to [`Input`] nodes.
960    ///
961    /// In `hugr-model` control flow regions have a single source and target port,
962    /// respectively, mirroring data flow regions. The region's source port needs
963    /// to be connected to either the input port of a block in the region or to the
964    /// target port.
965    fn import_cfg_region(
966        &mut self,
967        region: table::RegionId,
968        node: Node,
969    ) -> Result<(), ImportErrorInner> {
970        let region_data = self.get_region(region)?;
971
972        if region_data.kind != model::RegionKind::ControlFlow {
973            return Err(error_invalid!("expected cfg region"));
974        }
975
976        let prev_region = self.region_scope;
977        if region_data.scope.is_some() {
978            self.region_scope = region;
979        }
980
981        let region_target_types = (|| {
982            let [_, region_targets] = self.get_ctrl_type(
983                region_data
984                    .signature
985                    .ok_or_else(|| error_uninferred!("region signature"))?,
986            )?;
987
988            self.import_closed_list(region_targets)
989        })()
990        .map_err(|err| error_context!(err, "signature of cfg region with id {}", region))?;
991
992        // Identify the entry node of the control flow region by looking for
993        // a block whose input is linked to the sole source port of the CFG region.
994        let entry_node = 'find_entry: {
995            let [entry_link] = region_data.sources else {
996                return Err(error_invalid!("cfg region expects a single source"));
997            };
998
999            for child in region_data.children {
1000                let child_data = self.get_node(*child)?;
1001                let is_entry = child_data.inputs.iter().any(|link| link == entry_link);
1002
1003                if is_entry {
1004                    break 'find_entry *child;
1005                }
1006            }
1007
1008            // TODO: We should allow for the case in which control flows
1009            // directly from the source to the target of the region. This is
1010            // currently not allowed in hugr core directly, but may be simulated
1011            // by constructing an empty entry block.
1012            return Err(error_invalid!("cfg region without entry node"));
1013        };
1014
1015        // The entry node in core control flow regions is identified by being
1016        // the first child node of the CFG node. We therefore import the entry node first.
1017        self.import_node(entry_node, node)?;
1018
1019        // Create the exit node for the control flow region. This always needs
1020        // to be second in the node list.
1021        {
1022            let cfg_outputs = {
1023                let [target_types] = region_target_types.as_slice() else {
1024                    return Err(error_invalid!("cfg region expects a single target"));
1025                };
1026
1027                self.import_type_row(*target_types)?
1028            };
1029
1030            let exit = self
1031                .hugr
1032                .add_node_with_parent(node, OpType::ExitBlock(ExitBlock { cfg_outputs }));
1033            self.record_links(exit, Direction::Incoming, region_data.targets);
1034        }
1035
1036        // Finally we import all other nodes.
1037        for child in region_data.children {
1038            if *child != entry_node {
1039                self.import_node(*child, node)?;
1040            }
1041        }
1042
1043        for meta_item in region_data.meta {
1044            self.import_node_metadata(node, *meta_item)
1045                .map_err(|err| error_context!(err, "node metadata"))?;
1046        }
1047
1048        self.region_scope = prev_region;
1049
1050        Ok(())
1051    }
1052
1053    fn import_node_block(
1054        &mut self,
1055        node_id: table::NodeId,
1056        parent: Node,
1057    ) -> Result<Node, ImportErrorInner> {
1058        let node_data = self.get_node(node_id)?;
1059        debug_assert_eq!(node_data.operation, table::Operation::Block);
1060
1061        let [region] = node_data.regions else {
1062            return Err(error_invalid!("basic block expects a single region"));
1063        };
1064        let region_data = self.get_region(*region)?;
1065        let [inputs, outputs] = self.get_func_type(
1066            region_data
1067                .signature
1068                .ok_or_else(|| error_uninferred!("region signature"))?,
1069        )?;
1070        let inputs = self.import_type_row(inputs)?;
1071        let (sum_rows, other_outputs) = self.import_adt_and_rest(outputs)?;
1072
1073        let optype = OpType::DataflowBlock(DataflowBlock {
1074            inputs,
1075            other_outputs,
1076            sum_rows,
1077        });
1078        let node = self.make_node(node_id, optype, parent)?;
1079
1080        self.import_dfg_region(*region, node).map_err(|err| {
1081            error_context!(err, "block body defined by region with id {}", *region)
1082        })?;
1083        Ok(node)
1084    }
1085
1086    fn import_node_define_func(
1087        &mut self,
1088        node_id: table::NodeId,
1089        symbol: &'a table::Symbol<'a>,
1090        node_data: &'a table::Node<'a>,
1091        parent: Node,
1092    ) -> Result<Node, ImportErrorInner> {
1093        let visibility = symbol.visibility.clone().ok_or(ImportErrorInner::Invalid(
1094            "No visibility for FuncDefn".to_string(),
1095        ))?;
1096        self.import_poly_func_type(node_id, *symbol, |ctx, signature| {
1097            let func_name = ctx.import_title_metadata(node_id)?.unwrap_or(symbol.name);
1098            if visibility == model::Visibility::Public {
1099                ctx.description.extend_public_symbols([func_name.into()]);
1100            }
1101            let optype =
1102                OpType::FuncDefn(FuncDefn::new_vis(func_name, signature, visibility.into()));
1103
1104            let node = ctx.make_node(node_id, optype, parent)?;
1105
1106            let [region] = node_data.regions else {
1107                return Err(error_invalid!(
1108                    "function definition nodes expect a single region"
1109                ));
1110            };
1111
1112            ctx.import_dfg_region(*region, node).map_err(|err| {
1113                error_context!(err, "function body defined by region with id {}", *region)
1114            })?;
1115
1116            Ok(node)
1117        })
1118    }
1119
1120    fn import_node_declare_func(
1121        &mut self,
1122        node_id: table::NodeId,
1123        symbol: &'a table::Symbol<'a>,
1124        parent: Node,
1125    ) -> Result<Node, ImportErrorInner> {
1126        let visibility = symbol.visibility.clone().ok_or(ImportErrorInner::Invalid(
1127            "No visibility for FuncDecl".to_string(),
1128        ))?;
1129        self.import_poly_func_type(node_id, *symbol, |ctx, signature| {
1130            let func_name = ctx.import_title_metadata(node_id)?.unwrap_or(symbol.name);
1131            if visibility == model::Visibility::Public {
1132                ctx.description.extend_public_symbols([func_name.into()]);
1133            }
1134            let optype =
1135                OpType::FuncDecl(FuncDecl::new_vis(func_name, signature, visibility.into()));
1136            let node = ctx.make_node(node_id, optype, parent)?;
1137            Ok(node)
1138        })
1139    }
1140
1141    /// Import a node with a custom operation.
1142    ///
1143    /// A custom operation in `hugr-model` referred to by a symbol application
1144    /// term. The name of the symbol specifies the name of the custom operation,
1145    /// and the arguments supplied to the symbol are the arguments to be passed
1146    /// to the custom operation. This method imports the custom operations as
1147    /// [`OpaqueOp`]s. The [`OpaqueOp`]s are then resolved later against the
1148    /// [`ExtensionRegistry`].
1149    ///
1150    /// Some operations that needed to be builtins in `hugr-core` are custom
1151    /// operations in `hugr-model`. This method detects these and converts them
1152    /// to the corresponding `hugr-core` builtins.
1153    fn import_node_custom(
1154        &mut self,
1155        node_id: table::NodeId,
1156        operation: table::TermId,
1157        node_data: &'a table::Node<'a>,
1158        parent: Node,
1159    ) -> Result<Node, ImportErrorInner> {
1160        if let Some([inputs, outputs]) = self.match_symbol(operation, model::CORE_CALL_INDIRECT)? {
1161            let inputs = self.import_type_row(inputs)?;
1162            let outputs = self.import_type_row(outputs)?;
1163            let signature = Signature::new(inputs, outputs);
1164            let optype = OpType::CallIndirect(CallIndirect { signature });
1165            let node = self.make_node(node_id, optype, parent)?;
1166            return Ok(node);
1167        }
1168
1169        if let Some([_, _, func]) = self.match_symbol(operation, model::CORE_CALL)? {
1170            let (symbol, args) = match self.get_term(func)? {
1171                table::Term::Apply(symbol, args) => (symbol, args),
1172                table::Term::Var(_) => {
1173                    // TODO: Allow calling functions that are passed as variables.
1174                    //
1175                    // This would be necessary to allow functions which take
1176                    // other functions as static parameters and then call them.
1177                    // See #2301.
1178                    return Err(error_unsupported!(
1179                        "`{}` does not yet support function variables.",
1180                        model::CORE_CALL
1181                    ));
1182                }
1183                table::Term::Func(_) => {
1184                    // TODO: Allow importing and calling anonymous functions.
1185                    //
1186                    // This could be implemented in `hugr-core` by lifting the anonymous function
1187                    // into a function to be added into the containing module and then calling that
1188                    // function. See #2559.
1189                    return Err(error_unsupported!(
1190                        "`{}` does not yet support anonymous functions.",
1191                        model::CORE_CALL
1192                    ));
1193                }
1194                _ => {
1195                    return Err(error_invalid!(
1196                        concat!(
1197                            "Expected a function to be passed to `{}`.\n",
1198                            "Currently this is restricted to symbols that refer to functions."
1199                        ),
1200                        model::CORE_CALL
1201                    ));
1202                }
1203            };
1204
1205            let func_sig = self.get_func_signature(*symbol)?;
1206
1207            let type_args = args
1208                .iter()
1209                .map(|term| self.import_term(*term))
1210                .collect::<Result<Vec<TypeArg>, _>>()?;
1211
1212            self.static_edges.push((*symbol, node_id));
1213            let optype = OpType::Call(
1214                Call::try_new(func_sig, type_args).map_err(ImportErrorInner::Signature)?,
1215            );
1216
1217            let node = self.make_node(node_id, optype, parent)?;
1218            return Ok(node);
1219        }
1220
1221        if let Some([_, value]) = self.match_symbol(operation, model::CORE_LOAD_CONST)? {
1222            // If the constant refers directly to a function, import this as the `LoadFunc` operation.
1223            if let table::Term::Apply(symbol, args) = self.get_term(value)? {
1224                let func_node_data = self.get_node(*symbol)?;
1225
1226                if let table::Operation::DefineFunc(_) | table::Operation::DeclareFunc(_) =
1227                    func_node_data.operation
1228                {
1229                    let func_sig = self.get_func_signature(*symbol)?;
1230                    let type_args = args
1231                        .iter()
1232                        .map(|term| self.import_term(*term))
1233                        .collect::<Result<Vec<TypeArg>, _>>()?;
1234
1235                    self.static_edges.push((*symbol, node_id));
1236
1237                    let optype = OpType::LoadFunction(
1238                        LoadFunction::try_new(func_sig, type_args)
1239                            .map_err(ImportErrorInner::Signature)?,
1240                    );
1241
1242                    let node = self.make_node(node_id, optype, parent)?;
1243                    return Ok(node);
1244                }
1245            }
1246
1247            // Otherwise use const nodes
1248            let signature = node_data
1249                .signature
1250                .ok_or_else(|| error_uninferred!("node signature"))?;
1251            let [_, outputs] = self.get_func_type(signature)?;
1252            let outputs = self.import_closed_list(outputs)?;
1253            let output = outputs.first().ok_or_else(|| {
1254                error_invalid!("`{}` expects a single output", model::CORE_LOAD_CONST)
1255            })?;
1256            let datatype = self.import_type(*output)?;
1257
1258            let imported_value = self.import_value(value, *output)?;
1259
1260            let load_const_node = self.make_node(
1261                node_id,
1262                OpType::LoadConstant(LoadConstant {
1263                    datatype: datatype.clone(),
1264                }),
1265                parent,
1266            )?;
1267
1268            let const_node = self
1269                .hugr
1270                .add_node_with_parent(parent, OpType::Const(Const::new(imported_value)));
1271
1272            self.hugr.connect(const_node, 0, load_const_node, 0);
1273
1274            return Ok(load_const_node);
1275        }
1276
1277        if let Some([_, _, tag]) = self.match_symbol(operation, model::CORE_MAKE_ADT)? {
1278            let tag = match self.get_term(tag)? {
1279                table::Term::Literal(model::Literal::Nat(tag)) => tag,
1280                table::Term::Var(_) => {
1281                    return Err(error_unsupported!(
1282                        concat!(
1283                            "`{}` does not yet support passing a variable as the tag.\n",
1284                            "The `hugr-core` builtin `Tag` operation expects a concrete value for the tag. ",
1285                            "Therefore we must insist on a tag given as a natural number literal on import.",
1286                        ),
1287                        model::CORE_MAKE_ADT
1288                    ));
1289                }
1290                _ => {
1291                    return Err(error_invalid!(
1292                        "`{}` expects a nat literal tag",
1293                        model::CORE_MAKE_ADT
1294                    ));
1295                }
1296            };
1297
1298            let signature = node_data
1299                .signature
1300                .ok_or_else(|| error_uninferred!("node signature"))?;
1301            let [_, outputs] = self.get_func_type(signature)?;
1302            let (variants, _) = self.import_adt_and_rest(outputs)?;
1303            let node = self.make_node(
1304                node_id,
1305                OpType::Tag(Tag {
1306                    variants,
1307                    tag: *tag as usize,
1308                }),
1309                parent,
1310            )?;
1311            return Ok(node);
1312        }
1313
1314        let table::Term::Apply(node, params) = self.get_term(operation)? else {
1315            return Err(error_invalid!(
1316                "custom operations expect a symbol application referencing an operation"
1317            ));
1318        };
1319        let name = self.get_symbol_name(*node)?;
1320        let args = params
1321            .iter()
1322            .map(|param| self.import_term(*param))
1323            .collect::<Result<Vec<_>, _>>()?;
1324        let (extension, name) = self.import_custom_name(name)?;
1325        let signature = self.get_node_signature(node_id)?;
1326
1327        // TODO: Currently we do not have the description or any other metadata for
1328        // the custom op. This will improve with declarative extensions being able
1329        // to declare operations as a node, in which case the description will be attached
1330        // to that node as metadata.
1331
1332        let optype = OpType::OpaqueOp(OpaqueOp::new(extension, name, args, signature));
1333        self.make_node(node_id, optype, parent)
1334    }
1335
1336    fn import_node_define_alias(
1337        &mut self,
1338        node_id: table::NodeId,
1339        symbol: &'a table::Symbol<'a>,
1340        value: table::TermId,
1341        parent: Node,
1342    ) -> Result<Node, ImportErrorInner> {
1343        if !symbol.params.is_empty() {
1344            return Err(error_unsupported!(
1345                "parameters or constraints in alias definition"
1346            ));
1347        }
1348
1349        let optype = OpType::AliasDefn(AliasDefn {
1350            name: symbol.name.to_smolstr(),
1351            definition: self.import_type(value)?,
1352        });
1353
1354        let node = self.make_node(node_id, optype, parent)?;
1355        Ok(node)
1356    }
1357
1358    fn import_node_declare_alias(
1359        &mut self,
1360        node_id: table::NodeId,
1361        symbol: &'a table::Symbol<'a>,
1362        parent: Node,
1363    ) -> Result<Node, ImportErrorInner> {
1364        if !symbol.params.is_empty() {
1365            return Err(error_unsupported!(
1366                "parameters or constraints in alias declaration"
1367            ));
1368        }
1369
1370        let optype = OpType::AliasDecl(AliasDecl {
1371            name: symbol.name.to_smolstr(),
1372            bound: TypeBound::Copyable,
1373        });
1374
1375        let node = self.make_node(node_id, optype, parent)?;
1376        Ok(node)
1377    }
1378
1379    fn import_poly_func_type<RV: MaybeRV, T>(
1380        &mut self,
1381        node: table::NodeId,
1382        symbol: table::Symbol<'a>,
1383        in_scope: impl FnOnce(&mut Self, PolyFuncTypeBase<RV>) -> Result<T, ImportErrorInner>,
1384    ) -> Result<T, ImportErrorInner> {
1385        (|| {
1386            let mut imported_params = Vec::with_capacity(symbol.params.len());
1387
1388            for (index, param) in symbol.params.iter().enumerate() {
1389                self.local_vars
1390                    .insert(table::VarId(node, index as _), LocalVar::new(param.r#type));
1391            }
1392
1393            for constraint in symbol.constraints {
1394                if let Some([term]) = self.match_symbol(*constraint, model::CORE_NON_LINEAR)? {
1395                    let table::Term::Var(var) = self.get_term(term)? else {
1396                        return Err(error_unsupported!(
1397                            "constraint on term that is not a variable"
1398                        ));
1399                    };
1400
1401                    self.local_vars
1402                        .get_mut(var)
1403                        .ok_or_else(|| error_invalid!("unknown variable {}", var))?
1404                        .bound = TypeBound::Copyable;
1405                } else {
1406                    return Err(error_unsupported!(
1407                        concat!(
1408                            "Constraints other than `{}` can not yet be imported.\n",
1409                            "`hugr-core` does not have support for arbitrary constraints yet, ",
1410                            "instead relying on operation-specific Rust code to compute and ",
1411                            "validate signatures of custom operations."
1412                        ),
1413                        model::CORE_NON_LINEAR
1414                    ));
1415                }
1416            }
1417
1418            for (index, param) in symbol.params.iter().enumerate() {
1419                let bound = self.local_vars[&table::VarId(node, index as _)].bound;
1420                imported_params.push(
1421                    self.import_term_with_bound(param.r#type, bound)
1422                        .map_err(|err| error_context!(err, "type of parameter `{}`", param.name))?,
1423                );
1424            }
1425
1426            let body = self.import_func_type::<RV>(symbol.signature)?;
1427            in_scope(self, PolyFuncTypeBase::new(imported_params, body))
1428        })()
1429        .map_err(|err| error_context!(err, "symbol `{}` defined by node {}", symbol.name, node))
1430    }
1431
1432    /// Import a [`Term`] from a term that represents a static type or value.
1433    fn import_term(&mut self, term_id: table::TermId) -> Result<Term, ImportErrorInner> {
1434        self.import_term_with_bound(term_id, TypeBound::Linear)
1435    }
1436
1437    fn import_term_with_bound(
1438        &mut self,
1439        term_id: table::TermId,
1440        bound: TypeBound,
1441    ) -> Result<Term, ImportErrorInner> {
1442        (|| {
1443            if let Some([]) = self.match_symbol(term_id, model::CORE_STR_TYPE)? {
1444                return Ok(Term::StringType);
1445            }
1446
1447            if let Some([]) = self.match_symbol(term_id, model::CORE_NAT_TYPE)? {
1448                return Ok(Term::max_nat_type());
1449            }
1450
1451            if let Some([]) = self.match_symbol(term_id, model::CORE_BYTES_TYPE)? {
1452                return Ok(Term::BytesType);
1453            }
1454
1455            if let Some([]) = self.match_symbol(term_id, model::CORE_FLOAT_TYPE)? {
1456                return Ok(Term::FloatType);
1457            }
1458
1459            if let Some([]) = self.match_symbol(term_id, model::CORE_TYPE)? {
1460                return Ok(TypeParam::RuntimeType(bound));
1461            }
1462
1463            if let Some([]) = self.match_symbol(term_id, model::CORE_CONSTRAINT)? {
1464                return Err(error_unsupported!("`{}`", model::CORE_CONSTRAINT));
1465            }
1466
1467            if let Some([]) = self.match_symbol(term_id, model::CORE_STATIC)? {
1468                return Ok(Term::StaticType);
1469            }
1470
1471            if let Some([ty]) = self.match_symbol(term_id, model::CORE_CONST)? {
1472                let ty = self
1473                    .import_type(ty)
1474                    .map_err(|err| error_context!(err, "type of a constant"))?;
1475                return Ok(TypeParam::new_const(ty));
1476            }
1477
1478            if let Some([item_type]) = self.match_symbol(term_id, model::CORE_LIST_TYPE)? {
1479                // At present `hugr-model` has no way to express that the item
1480                // type of a list must be copyable. Therefore we import it as `Any`.
1481                let item_type = self
1482                    .import_term(item_type)
1483                    .map_err(|err| error_context!(err, "item type of list type"))?;
1484                return Ok(TypeParam::new_list_type(item_type));
1485            }
1486
1487            if let Some([item_types]) = self.match_symbol(term_id, model::CORE_TUPLE_TYPE)? {
1488                // At present `hugr-model` has no way to express that the item
1489                // types of a tuple must be copyable. Therefore we import it as `Any`.
1490                let item_types = self
1491                    .import_term(item_types)
1492                    .map_err(|err| error_context!(err, "item types of tuple type"))?;
1493                return Ok(TypeParam::new_tuple_type(item_types));
1494            }
1495
1496            match self.get_term(term_id)? {
1497                table::Term::Wildcard => Err(error_uninferred!("wildcard")),
1498
1499                table::Term::Var(var) => {
1500                    let var_info = self
1501                        .local_vars
1502                        .get(var)
1503                        .ok_or_else(|| error_invalid!("unknown variable {}", var))?;
1504                    let decl = self.import_term_with_bound(var_info.r#type, var_info.bound)?;
1505                    Ok(Term::new_var_use(var.1 as _, decl))
1506                }
1507
1508                table::Term::List(parts) => {
1509                    // PERFORMANCE: Can we do this without the additional allocation?
1510                    let parts: Vec<_> = parts
1511                        .iter()
1512                        .map(|part| self.import_seq_part(part))
1513                        .collect::<Result<_, _>>()
1514                        .map_err(|err| error_context!(err, "list parts"))?;
1515                    Ok(TypeArg::new_list_from_parts(parts))
1516                }
1517
1518                table::Term::Tuple(parts) => {
1519                    // PERFORMANCE: Can we do this without the additional allocation?
1520                    let parts: Vec<_> = parts
1521                        .iter()
1522                        .map(|part| self.import_seq_part(part))
1523                        .try_collect()
1524                        .map_err(|err| error_context!(err, "tuple parts"))?;
1525                    Ok(TypeArg::new_tuple_from_parts(parts))
1526                }
1527
1528                table::Term::Literal(model::Literal::Str(value)) => {
1529                    Ok(Term::String(value.to_string()))
1530                }
1531
1532                table::Term::Literal(model::Literal::Nat(value)) => Ok(Term::BoundedNat(*value)),
1533
1534                table::Term::Literal(model::Literal::Bytes(value)) => {
1535                    Ok(Term::Bytes(value.clone()))
1536                }
1537                table::Term::Literal(model::Literal::Float(value)) => Ok(Term::Float(*value)),
1538                table::Term::Func { .. } => Err(error_unsupported!("function constant")),
1539
1540                table::Term::Apply { .. } => {
1541                    let ty: Type = self.import_type(term_id)?;
1542                    Ok(ty.into())
1543                }
1544            }
1545        })()
1546        .map_err(|err| error_context!(err, "term {}", term_id))
1547    }
1548
1549    fn import_seq_part(
1550        &mut self,
1551        seq_part: &'a table::SeqPart,
1552    ) -> Result<SeqPart<TypeArg>, ImportErrorInner> {
1553        Ok(match seq_part {
1554            table::SeqPart::Item(term_id) => SeqPart::Item(self.import_term(*term_id)?),
1555            table::SeqPart::Splice(term_id) => SeqPart::Splice(self.import_term(*term_id)?),
1556        })
1557    }
1558
1559    /// Import a `Type` from a term that represents a runtime type.
1560    fn import_type<RV: MaybeRV>(
1561        &mut self,
1562        term_id: table::TermId,
1563    ) -> Result<TypeBase<RV>, ImportErrorInner> {
1564        (|| {
1565            if let Some([_, _]) = self.match_symbol(term_id, model::CORE_FN)? {
1566                let func_type = self.import_func_type::<RowVariable>(term_id)?;
1567                return Ok(TypeBase::new_function(func_type));
1568            }
1569
1570            if let Some([variants]) = self.match_symbol(term_id, model::CORE_ADT)? {
1571                let variants = (|| {
1572                    self.import_closed_list(variants)?
1573                        .iter()
1574                        .map(|variant| self.import_type_row::<RowVariable>(*variant))
1575                        .collect::<Result<Vec<_>, _>>()
1576                })()
1577                .map_err(|err| error_context!(err, "adt variants"))?;
1578
1579                return Ok(TypeBase::new_sum(variants));
1580            }
1581
1582            match self.get_term(term_id)? {
1583                table::Term::Wildcard => Err(error_uninferred!("wildcard")),
1584
1585                table::Term::Apply(symbol, args) => {
1586                    let name = self.get_symbol_name(*symbol)?;
1587
1588                    let args = args
1589                        .iter()
1590                        .map(|arg| self.import_term(*arg))
1591                        .collect::<Result<Vec<_>, _>>()
1592                        .map_err(|err| {
1593                            error_context!(err, "type argument of custom type `{}`", name)
1594                        })?;
1595
1596                    let (extension, id) = self.import_custom_name(name)?;
1597
1598                    let extension_ref =
1599                        self.extensions
1600                            .get(&extension)
1601                            .ok_or_else(|| ExtensionError::Missing {
1602                                missing_ext: extension.clone(),
1603                                available: self.extensions.ids().cloned().collect(),
1604                            })?;
1605
1606                    let ext_type =
1607                        extension_ref
1608                            .get_type(&id)
1609                            .ok_or_else(|| ExtensionError::MissingType {
1610                                ext: extension.clone(),
1611                                name: id.clone(),
1612                            })?;
1613
1614                    let bound = ext_type.bound(&args);
1615
1616                    Ok(TypeBase::new_extension(CustomType::new(
1617                        id,
1618                        args,
1619                        extension,
1620                        bound,
1621                        &Arc::downgrade(extension_ref),
1622                    )))
1623                }
1624
1625                table::Term::Var(var @ table::VarId(_, index)) => {
1626                    let local_var = self
1627                        .local_vars
1628                        .get(var)
1629                        .ok_or(error_invalid!("unknown var {}", var))?;
1630                    Ok(TypeBase::new_var_use(*index as _, local_var.bound))
1631                }
1632
1633                // The following terms are not runtime types, but the core `Type` only contains runtime types.
1634                // We therefore report a type error here.
1635                table::Term::Literal(_)
1636                | table::Term::List { .. }
1637                | table::Term::Tuple { .. }
1638                | table::Term::Func { .. } => Err(error_invalid!("expected a runtime type")),
1639            }
1640        })()
1641        .map_err(|err| error_context!(err, "term {} as `Type`", term_id))
1642    }
1643
1644    fn get_func_type(
1645        &mut self,
1646        term_id: table::TermId,
1647    ) -> Result<[table::TermId; 2], ImportErrorInner> {
1648        self.match_symbol(term_id, model::CORE_FN)?
1649            .ok_or(error_invalid!("expected a function type"))
1650    }
1651
1652    fn get_ctrl_type(
1653        &mut self,
1654        term_id: table::TermId,
1655    ) -> Result<[table::TermId; 2], ImportErrorInner> {
1656        self.match_symbol(term_id, model::CORE_CTRL)?
1657            .ok_or(error_invalid!("expected a control type"))
1658    }
1659
1660    /// Import a [`Signature`] or [`FuncValueType`].
1661    ///
1662    /// When importing a [`Signature`] the lists of input and output types need
1663    /// to be closed. In contrast [`FuncValueType`] admits importing open lists
1664    /// of input and output types via "row variables".
1665    ///
1666    /// Function types are not special-cased in `hugr-model` but are represented
1667    /// via the `core.fn` term constructor.
1668    fn import_func_type<RV: MaybeRV>(
1669        &mut self,
1670        term_id: table::TermId,
1671    ) -> Result<FuncTypeBase<RV>, ImportErrorInner> {
1672        (|| {
1673            let [inputs, outputs] = self.get_func_type(term_id)?;
1674            let inputs = self
1675                .import_type_row(inputs)
1676                .map_err(|err| error_context!(err, "function inputs"))?;
1677            let outputs = self
1678                .import_type_row(outputs)
1679                .map_err(|err| error_context!(err, "function outputs"))?;
1680            Ok(FuncTypeBase::new(inputs, outputs))
1681        })()
1682        .map_err(|err| error_context!(err, "function type"))
1683    }
1684
1685    /// Import a closed list as a vector of term ids.
1686    ///
1687    /// This method supports list terms that contain spliced sublists as long as
1688    /// the list can be recursively flattened to only contain individual items.
1689    ///
1690    /// To allow for IR constructions that are parameterised by static
1691    /// parameters, open lists with spliced variables should be supported where
1692    /// possible. Closed lists might be required in some places of the IR that
1693    /// are not supposed to be parameterised with variables or where such
1694    /// parameterisation is not yet supported by the `hugr-core` structures that
1695    /// we are importing into.
1696    fn import_closed_list(
1697        &mut self,
1698        term_id: table::TermId,
1699    ) -> Result<Vec<table::TermId>, ImportErrorInner> {
1700        fn import_into(
1701            ctx: &mut Context,
1702            term_id: table::TermId,
1703            types: &mut Vec<table::TermId>,
1704        ) -> Result<(), ImportErrorInner> {
1705            match ctx.get_term(term_id)? {
1706                table::Term::List(parts) => {
1707                    types.reserve(parts.len());
1708
1709                    for part in *parts {
1710                        match part {
1711                            table::SeqPart::Item(term_id) => {
1712                                types.push(*term_id);
1713                            }
1714                            table::SeqPart::Splice(term_id) => {
1715                                import_into(ctx, *term_id, types)?;
1716                            }
1717                        }
1718                    }
1719                }
1720                _ => {
1721                    return Err(error_invalid!(
1722                        "Expected a closed list.\n{}",
1723                        CLOSED_LIST_HINT
1724                    ));
1725                }
1726            }
1727
1728            Ok(())
1729        }
1730
1731        let mut types = Vec::new();
1732        import_into(self, term_id, &mut types)?;
1733        Ok(types)
1734    }
1735
1736    /// Import a closed tuple as a vector of term ids.
1737    ///
1738    /// This is the tuple version of [`Self::import_closed_list`].
1739    fn import_closed_tuple(
1740        &mut self,
1741        term_id: table::TermId,
1742    ) -> Result<Vec<table::TermId>, ImportErrorInner> {
1743        fn import_into(
1744            ctx: &mut Context,
1745            term_id: table::TermId,
1746            types: &mut Vec<table::TermId>,
1747        ) -> Result<(), ImportErrorInner> {
1748            match ctx.get_term(term_id)? {
1749                table::Term::Tuple(parts) => {
1750                    types.reserve(parts.len());
1751
1752                    for part in *parts {
1753                        match part {
1754                            table::SeqPart::Item(term_id) => {
1755                                types.push(*term_id);
1756                            }
1757                            table::SeqPart::Splice(term_id) => {
1758                                import_into(ctx, *term_id, types)?;
1759                            }
1760                        }
1761                    }
1762                }
1763                _ => {
1764                    return Err(error_invalid!(
1765                        "Expected a closed tuple term.\n{}",
1766                        CLOSED_TUPLE_HINT
1767                    ));
1768                }
1769            }
1770
1771            Ok(())
1772        }
1773
1774        let mut types = Vec::new();
1775        import_into(self, term_id, &mut types)?;
1776        Ok(types)
1777    }
1778
1779    /// Imports a list of lists as a vector of type rows.
1780    ///
1781    /// See [`Self::import_type_row`].
1782    fn import_type_rows<RV: MaybeRV>(
1783        &mut self,
1784        term_id: table::TermId,
1785    ) -> Result<Vec<TypeRowBase<RV>>, ImportErrorInner> {
1786        self.import_closed_list(term_id)?
1787            .into_iter()
1788            .map(|term_id| self.import_type_row::<RV>(term_id))
1789            .collect()
1790    }
1791
1792    /// Imports a list as a type row.
1793    ///
1794    /// This method works to produce a [`TypeRow`] or a [`TypeRowRV`], depending
1795    /// on the `RV` type argument. For [`TypeRow`] a closed list is expected.
1796    /// For [`TypeRowRV`] we import spliced variables as row variables.
1797    fn import_type_row<RV: MaybeRV>(
1798        &mut self,
1799        term_id: table::TermId,
1800    ) -> Result<TypeRowBase<RV>, ImportErrorInner> {
1801        fn import_into<RV: MaybeRV>(
1802            ctx: &mut Context,
1803            term_id: table::TermId,
1804            types: &mut Vec<TypeBase<RV>>,
1805        ) -> Result<(), ImportErrorInner> {
1806            match ctx.get_term(term_id)? {
1807                table::Term::List(parts) => {
1808                    types.reserve(parts.len());
1809
1810                    for item in *parts {
1811                        match item {
1812                            table::SeqPart::Item(term_id) => {
1813                                types.push(ctx.import_type::<RV>(*term_id)?);
1814                            }
1815                            table::SeqPart::Splice(term_id) => {
1816                                import_into(ctx, *term_id, types)?;
1817                            }
1818                        }
1819                    }
1820                }
1821                table::Term::Var(table::VarId(_, index)) => {
1822                    let var = RV::try_from_rv(RowVariable(*index as _, TypeBound::Linear))
1823                        .map_err(|_| {
1824                            error_invalid!("Expected a closed list.\n{}", CLOSED_LIST_HINT)
1825                        })?;
1826                    types.push(TypeBase::new(TypeEnum::RowVar(var)));
1827                }
1828                _ => return Err(error_invalid!("expected a list")),
1829            }
1830
1831            Ok(())
1832        }
1833
1834        let mut types = Vec::new();
1835        import_into(self, term_id, &mut types)?;
1836        Ok(types.into())
1837    }
1838
1839    fn import_custom_name(
1840        &mut self,
1841        symbol: &'a str,
1842    ) -> Result<(ExtensionId, SmolStr), ImportErrorInner> {
1843        use std::collections::hash_map::Entry;
1844        match self.custom_name_cache.entry(symbol) {
1845            Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()),
1846            Entry::Vacant(vacant_entry) => {
1847                let qualified_name = ExtensionId::new(symbol)
1848                    .map_err(|_| error_invalid!("`{}` is not a valid symbol name", symbol))?;
1849
1850                let (extension, id) = qualified_name
1851                    .split_last()
1852                    .ok_or_else(|| error_invalid!("`{}` is not a valid symbol name", symbol))?;
1853
1854                vacant_entry.insert((extension.clone(), id.clone()));
1855                Ok((extension, id))
1856            }
1857        }
1858    }
1859
1860    /// Import a constant term as a [`Value`].
1861    ///
1862    /// This method supports the JSON compatibility constants and a small selection of built in
1863    /// constant constructors. It is a compatibility shim until constants can be represented as
1864    /// terms in `hugr-core` at which point this method will become redundant.
1865    fn import_value(
1866        &mut self,
1867        term_id: table::TermId,
1868        type_id: table::TermId,
1869    ) -> Result<Value, ImportErrorInner> {
1870        let term_data = self.get_term(term_id)?;
1871
1872        // NOTE: We have special cased arrays, integers, and floats for now.
1873        // TODO: Allow arbitrary extension values to be imported from terms.
1874
1875        if let Some([runtime_type, json]) = self.match_symbol(term_id, model::COMPAT_CONST_JSON)? {
1876            let table::Term::Literal(model::Literal::Str(json)) = self.get_term(json)? else {
1877                return Err(error_invalid!(
1878                    "`{}` expects a string literal",
1879                    model::COMPAT_CONST_JSON
1880                ));
1881            };
1882
1883            // We attempt to deserialize as the custom const directly.
1884            // This might fail due to the custom const struct not being included when
1885            // this code was compiled; in that case, we fall back to the serialized form.
1886            let value: Option<Box<dyn CustomConst>> = serde_json::from_str(json).ok();
1887
1888            if let Some(value) = value {
1889                let opaque_value = OpaqueValue::from(value);
1890                return Ok(Value::Extension { e: opaque_value });
1891            } else {
1892                let runtime_type = self.import_type(runtime_type)?;
1893                let value: serde_json::Value = serde_json::from_str(json).map_err(|_| {
1894                    error_invalid!(
1895                        "unable to parse JSON string for `{}`",
1896                        model::COMPAT_CONST_JSON
1897                    )
1898                })?;
1899                let custom_const = CustomSerialized::new(runtime_type, value);
1900                let opaque_value = OpaqueValue::new(custom_const);
1901                return Ok(Value::Extension { e: opaque_value });
1902            }
1903        }
1904
1905        if let Some([_, element_type_term, contents]) =
1906            self.match_symbol(term_id, ArrayValue::CTR_NAME)?
1907        {
1908            let element_type = self.import_type(element_type_term)?;
1909            let contents = self.import_closed_list(contents)?;
1910            let contents = contents
1911                .iter()
1912                .map(|item| self.import_value(*item, element_type_term))
1913                .collect::<Result<Vec<_>, _>>()?;
1914            return Ok(ArrayValue::new(element_type, contents).into());
1915        }
1916
1917        if let Some([bitwidth, value]) = self.match_symbol(term_id, ConstInt::CTR_NAME)? {
1918            let bitwidth = {
1919                let table::Term::Literal(model::Literal::Nat(bitwidth)) =
1920                    self.get_term(bitwidth)?
1921                else {
1922                    return Err(error_invalid!(
1923                        "`{}` expects a nat literal in its `bitwidth` argument",
1924                        ConstInt::CTR_NAME
1925                    ));
1926                };
1927                if *bitwidth > 6 {
1928                    return Err(error_invalid!(
1929                        "`{}` expects the bitwidth to be at most 6, got {}",
1930                        ConstInt::CTR_NAME,
1931                        bitwidth
1932                    ));
1933                }
1934                *bitwidth as u8
1935            };
1936
1937            let value = {
1938                let table::Term::Literal(model::Literal::Nat(value)) = self.get_term(value)? else {
1939                    return Err(error_invalid!(
1940                        "`{}` expects a nat literal value",
1941                        ConstInt::CTR_NAME
1942                    ));
1943                };
1944                *value
1945            };
1946
1947            return Ok(ConstInt::new_u(bitwidth, value)
1948                .map_err(|_| error_invalid!("failed to create int constant"))?
1949                .into());
1950        }
1951
1952        if let Some([value]) = self.match_symbol(term_id, ConstF64::CTR_NAME)? {
1953            let table::Term::Literal(model::Literal::Float(value)) = self.get_term(value)? else {
1954                return Err(error_invalid!(
1955                    "`{}` expects a float literal value",
1956                    ConstF64::CTR_NAME
1957                ));
1958            };
1959
1960            return Ok(ConstF64::new(value.into_inner()).into());
1961        }
1962
1963        if let Some([_, _, tag, values]) = self.match_symbol(term_id, model::CORE_CONST_ADT)? {
1964            let [variants] = self.expect_symbol(type_id, model::CORE_ADT)?;
1965            let values = self.import_closed_tuple(values)?;
1966            let variants = self.import_closed_list(variants)?;
1967
1968            let table::Term::Literal(model::Literal::Nat(tag)) = self.get_term(tag)? else {
1969                return Err(error_invalid!(
1970                    "`{}` expects a nat literal tag",
1971                    model::CORE_ADT
1972                ));
1973            };
1974
1975            let variant = variants.get(*tag as usize).ok_or(error_invalid!(
1976                "the tag of a `{}` must be a valid index into the list of variants",
1977                model::CORE_CONST_ADT
1978            ))?;
1979
1980            let variant = self.import_closed_list(*variant)?;
1981
1982            let items = values
1983                .iter()
1984                .zip(variant.iter())
1985                .map(|(value, ty)| self.import_value(*value, *ty))
1986                .collect::<Result<Vec<_>, _>>()?;
1987
1988            let ty = {
1989                // TODO: Import as a `SumType` directly and avoid the copy.
1990                let ty: Type = self.import_type(type_id)?;
1991                match ty.as_type_enum() {
1992                    TypeEnum::Sum(sum) => sum.clone(),
1993                    _ => unreachable!(),
1994                }
1995            };
1996
1997            return Ok(Value::sum(*tag as _, items, ty).unwrap());
1998        }
1999
2000        match term_data {
2001            table::Term::Wildcard => Err(error_uninferred!("wildcard")),
2002            table::Term::Var(_) => Err(error_unsupported!(concat!(
2003                "Constant value containing a variable.\n",
2004                "The constant system in `hugr-core` is not set up yet to support ",
2005                "constants that depend on variables.",
2006            ))),
2007
2008            table::Term::Apply(symbol, _) => {
2009                let symbol_name = self.get_symbol_name(*symbol)?;
2010                Err(error_unsupported!(
2011                    concat!(
2012                        "Unknown custom constant constructor `{}`.\n",
2013                        "Importing constants from `hugr-model` to `hugr-core` currently only supports a small ",
2014                        "and hard-coded list of constant constructors. To support JSON encoded constants ",
2015                        "use the constant constructor `{}`."
2016                    ),
2017                    symbol_name,
2018                    model::COMPAT_CONST_JSON
2019                ))
2020                // TODO: This should ultimately include the following cases:
2021                // - function definitions
2022                // - custom constructors for values
2023            }
2024
2025            table::Term::List { .. } | table::Term::Tuple(_) | table::Term::Literal(_) => {
2026                Err(error_invalid!("expected constant"))
2027            }
2028
2029            table::Term::Func { .. } => Err(error_unsupported!("Constant function value.")),
2030        }
2031    }
2032
2033    /// Check if a term is an application of a symbol with the given name. If
2034    /// so, return the arguments of the application.
2035    ///
2036    /// We allow the match even if the symbol is applied to fewer arguments than
2037    /// expected. In that case, the arguments are considered implicit and the
2038    /// return array is padded with wildcard terms at the beginning. The match
2039    /// fails if the symbol is applied to more arguments than expected.
2040    ///
2041    /// # Errors
2042    ///
2043    /// An error is returned in the following cases:
2044    ///
2045    /// - The term id does not exist in the module to be imported.
2046    /// - The term is a symbol application but the node id in the application does not refer to a symbol.
2047    ///
2048    /// Failed matches return `Ok(None)` instead of an error so that this method can be used
2049    /// in sequence to probe for applications of different symbol constructors.
2050    fn match_symbol<const N: usize>(
2051        &self,
2052        term_id: table::TermId,
2053        name: &str,
2054    ) -> Result<Option<[table::TermId; N]>, ImportErrorInner> {
2055        let term = self.get_term(term_id)?;
2056
2057        // TODO: Follow alias chains?
2058
2059        let table::Term::Apply(symbol, args) = term else {
2060            return Ok(None);
2061        };
2062
2063        if name != self.get_symbol_name(*symbol)? {
2064            return Ok(None);
2065        }
2066
2067        if args.len() > N {
2068            return Ok(None);
2069        }
2070
2071        let result = std::array::from_fn(|i| {
2072            (i + args.len())
2073                .checked_sub(N)
2074                .map(|i| args[i])
2075                .unwrap_or_default()
2076        });
2077
2078        Ok(Some(result))
2079    }
2080
2081    /// Expects a term to be an application of a symbol with the given name and
2082    /// returns the arguments of the application.
2083    ///
2084    /// See [`Self::match_symbol`].
2085    ///
2086    /// # Errors
2087    ///
2088    /// In addition to the error cases described in [`Self::match_symbol`], this
2089    /// method also returns an error when the match failed.
2090    fn expect_symbol<const N: usize>(
2091        &self,
2092        term_id: table::TermId,
2093        name: &str,
2094    ) -> Result<[table::TermId; N], ImportErrorInner> {
2095        self.match_symbol(term_id, name)?.ok_or(error_invalid!(
2096            "Expected symbol `{}` with arity {}.",
2097            name,
2098            N
2099        ))
2100    }
2101
2102    /// Searches for `core.title` metadata on the given node.
2103    ///
2104    /// The `core.title` metadata is used as the `hugr-core` name for private function symbols.
2105    /// This is necessary as a compatibility shim to bridge between the different concepts of name
2106    /// in `hugr-core` and `hugr-model`: In `hugr-model` the name of a symbol uniquely identifies
2107    /// that symbol within a module, while in `hugr-core` the name started out as procedurally
2108    /// irrelevant debug metadata. With linking, the `hugr-core` name has become significant,
2109    /// but only for public functions.
2110    fn import_title_metadata(
2111        &self,
2112        node_id: table::NodeId,
2113    ) -> Result<Option<&'a str>, ImportErrorInner> {
2114        let node_data = self.get_node(node_id)?;
2115        for meta in node_data.meta {
2116            let Some([name]) = self.match_symbol(*meta, model::CORE_TITLE)? else {
2117                continue;
2118            };
2119
2120            let table::Term::Literal(model::Literal::Str(name)) = self.get_term(name)? else {
2121                return Err(error_invalid!(
2122                    "`{}` metadata expected a string literal as argument",
2123                    model::CORE_TITLE
2124                ));
2125            };
2126
2127            return Ok(Some(name.as_str()));
2128        }
2129
2130        Ok(None)
2131    }
2132}
2133
2134/// Information about a local variable.
2135#[derive(Debug, Clone, Copy)]
2136struct LocalVar {
2137    /// The type of the variable.
2138    r#type: table::TermId,
2139    /// The type bound of the variable.
2140    bound: TypeBound,
2141}
2142
2143impl LocalVar {
2144    pub fn new(r#type: table::TermId) -> Self {
2145        Self {
2146            r#type,
2147            bound: TypeBound::Linear,
2148        }
2149    }
2150}