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    extension::{ExtensionId, ExtensionRegistry, SignatureError},
10    hugr::{HugrMut, NodeMetadata},
11    ops::{
12        constant::{CustomConst, CustomSerialized, OpaqueValue},
13        AliasDecl, AliasDefn, Call, CallIndirect, Case, Conditional, Const, DataflowBlock,
14        ExitBlock, FuncDecl, FuncDefn, Input, LoadConstant, LoadFunction, OpType, OpaqueOp, Output,
15        Tag, TailLoop, Value, CFG, DFG,
16    },
17    package::Package,
18    std_extensions::{
19        arithmetic::{float_types::ConstF64, int_types::ConstInt},
20        collections::array::ArrayValue,
21    },
22    types::{
23        type_param::TypeParam, type_row::TypeRowBase, CustomType, FuncTypeBase, MaybeRV,
24        PolyFuncType, PolyFuncTypeBase, RowVariable, Signature, Type, TypeArg, TypeBase, TypeBound,
25        TypeEnum, TypeName, TypeRow,
26    },
27    Direction, Hugr, HugrView, Node, Port,
28};
29use fxhash::FxHashMap;
30use hugr_model::v0 as model;
31use hugr_model::v0::table;
32use itertools::Either;
33use smol_str::{SmolStr, ToSmolStr};
34use thiserror::Error;
35
36/// Error during import.
37#[derive(Debug, Clone, Error)]
38#[non_exhaustive]
39pub enum ImportError {
40    /// The model contains a feature that is not supported by the importer yet.
41    /// Errors of this kind are expected to be removed as the model format and
42    /// the core HUGR representation converge.
43    #[error("currently unsupported: {0}")]
44    Unsupported(String),
45    /// The model contains implicit information that has not yet been inferred.
46    /// This includes wildcards and application of functions with implicit parameters.
47    #[error("uninferred implicit: {0}")]
48    Uninferred(String),
49    /// A signature mismatch was detected during import.
50    #[error("signature error: {0}")]
51    Signature(#[from] SignatureError),
52    /// A required extension is missing.
53    #[error("Importing the hugr requires extension {missing_ext}, which was not found in the registry. The available extensions are: [{}]",
54            available.iter().map(|ext| ext.to_string()).collect::<Vec<_>>().join(", "))]
55    Extension {
56        /// The missing extension.
57        missing_ext: ExtensionId,
58        /// The available extensions in the registry.
59        available: Vec<ExtensionId>,
60    },
61    /// An extension type is missing.
62    #[error("Importing the hugr requires extension {ext} to have a type named {name}, but it was not found.")]
63    ExtensionType {
64        /// The extension that is missing the type.
65        ext: ExtensionId,
66        /// The name of the missing type.
67        name: TypeName,
68    },
69    /// The model is not well-formed.
70    #[error("validate error: {0}")]
71    Model(#[from] table::ModelError),
72    /// Incorrect order hints.
73    #[error("incorrect order hint: {0}")]
74    OrderHint(#[from] OrderHintError),
75}
76
77/// Import error caused by incorrect order hints.
78#[derive(Debug, Clone, Error)]
79#[non_exhaustive]
80pub enum OrderHintError {
81    /// Duplicate order hint key in the same region.
82    #[error("duplicate order hint key {0}")]
83    DuplicateKey(table::NodeId, u64),
84    /// Order hint including a key not defined in the region.
85    #[error("order hint with unknown key {0}")]
86    UnknownKey(u64),
87    /// Order hint involving a node with no order port.
88    #[error("order hint on node with no order port: {0}")]
89    NoOrderPort(table::NodeId),
90}
91
92/// Helper macro to create an `ImportError::Unsupported` error with a formatted message.
93macro_rules! error_unsupported {
94    ($($e:expr),*) => { ImportError::Unsupported(format!($($e),*)) }
95}
96
97/// Helper macro to create an `ImportError::Uninferred` error with a formatted message.
98macro_rules! error_uninferred {
99    ($($e:expr),*) => { ImportError::Uninferred(format!($($e),*)) }
100}
101
102/// Import a [`Package`] from its model representation.
103pub fn import_package(
104    package: &table::Package,
105    extensions: &ExtensionRegistry,
106) -> Result<Package, ImportError> {
107    let modules = package
108        .modules
109        .iter()
110        .map(|module| import_hugr(module, extensions))
111        .collect::<Result<Vec<_>, _>>()?;
112
113    // This does not panic since the import already requires a module root.
114    let package = Package::new(modules);
115    Ok(package)
116}
117
118/// Import a [`Hugr`] module from its model representation.
119pub fn import_hugr(
120    module: &table::Module,
121    extensions: &ExtensionRegistry,
122) -> Result<Hugr, ImportError> {
123    // TODO: Module should know about the number of edges, so that we can use a vector here.
124    // For now we use a hashmap, which will be slower.
125    let mut ctx = Context {
126        module,
127        hugr: Hugr::new(),
128        link_ports: FxHashMap::default(),
129        static_edges: Vec::new(),
130        extensions,
131        nodes: FxHashMap::default(),
132        local_vars: FxHashMap::default(),
133        custom_name_cache: FxHashMap::default(),
134        region_scope: table::RegionId::default(),
135    };
136
137    ctx.import_root()?;
138    ctx.link_ports()?;
139    ctx.link_static_ports()?;
140
141    Ok(ctx.hugr)
142}
143
144struct Context<'a> {
145    /// The module being imported.
146    module: &'a table::Module<'a>,
147
148    /// The HUGR graph being constructed.
149    hugr: Hugr,
150
151    /// The ports that are part of each link. This is used to connect the ports at the end of the
152    /// import process.
153    link_ports: FxHashMap<(table::RegionId, table::LinkIndex), Vec<(Node, Port)>>,
154
155    /// Pairs of nodes that should be connected by a static edge.
156    /// These are collected during the import process and connected at the end.
157    static_edges: Vec<(table::NodeId, table::NodeId)>,
158
159    /// The ambient extension registry to use for importing.
160    extensions: &'a ExtensionRegistry,
161
162    /// A map from `NodeId` to the imported `Node`.
163    nodes: FxHashMap<table::NodeId, Node>,
164
165    local_vars: FxHashMap<table::VarId, LocalVar>,
166
167    custom_name_cache: FxHashMap<&'a str, (ExtensionId, SmolStr)>,
168
169    region_scope: table::RegionId,
170}
171
172impl<'a> Context<'a> {
173    /// Get the signature of the node with the given `NodeId`.
174    fn get_node_signature(&mut self, node: table::NodeId) -> Result<Signature, ImportError> {
175        let node_data = self.get_node(node)?;
176        let signature = node_data
177            .signature
178            .ok_or_else(|| error_uninferred!("node signature"))?;
179        self.import_func_type(signature)
180    }
181
182    /// Get the node with the given `NodeId`, or return an error if it does not exist.
183    #[inline]
184    fn get_node(&self, node_id: table::NodeId) -> Result<&'a table::Node<'a>, ImportError> {
185        self.module
186            .get_node(node_id)
187            .ok_or_else(|| table::ModelError::NodeNotFound(node_id).into())
188    }
189
190    /// Get the term with the given `TermId`, or return an error if it does not exist.
191    #[inline]
192    fn get_term(&self, term_id: table::TermId) -> Result<&'a table::Term<'a>, ImportError> {
193        self.module
194            .get_term(term_id)
195            .ok_or_else(|| table::ModelError::TermNotFound(term_id).into())
196    }
197
198    /// Get the region with the given `RegionId`, or return an error if it does not exist.
199    #[inline]
200    fn get_region(&self, region_id: table::RegionId) -> Result<&'a table::Region<'a>, ImportError> {
201        self.module
202            .get_region(region_id)
203            .ok_or_else(|| table::ModelError::RegionNotFound(region_id).into())
204    }
205
206    fn make_node(
207        &mut self,
208        node_id: table::NodeId,
209        op: OpType,
210        parent: Node,
211    ) -> Result<Node, ImportError> {
212        let node = self.hugr.add_node_with_parent(parent, op);
213        self.nodes.insert(node_id, node);
214
215        let node_data = self.get_node(node_id)?;
216        self.record_links(node, Direction::Incoming, node_data.inputs);
217        self.record_links(node, Direction::Outgoing, node_data.outputs);
218
219        for meta_item in node_data.meta {
220            self.import_node_metadata(node, *meta_item)?;
221        }
222
223        Ok(node)
224    }
225
226    fn import_node_metadata(
227        &mut self,
228        node: Node,
229        meta_item: table::TermId,
230    ) -> Result<(), ImportError> {
231        // Import the JSON metadata
232        if let Some([name_arg, json_arg]) = self.match_symbol(meta_item, model::COMPAT_META_JSON)? {
233            let table::Term::Literal(model::Literal::Str(name)) = self.get_term(name_arg)? else {
234                return Err(table::ModelError::TypeError(meta_item).into());
235            };
236
237            let table::Term::Literal(model::Literal::Str(json_str)) = self.get_term(json_arg)?
238            else {
239                return Err(table::ModelError::TypeError(meta_item).into());
240            };
241
242            let json_value: NodeMetadata = serde_json::from_str(json_str)
243                .map_err(|_| table::ModelError::TypeError(meta_item))?;
244
245            self.hugr.set_metadata(node, name, json_value);
246        }
247
248        // Set the entrypoint
249        if let Some([]) = self.match_symbol(meta_item, model::CORE_ENTRYPOINT)? {
250            self.hugr.set_entrypoint(node);
251        }
252
253        Ok(())
254    }
255
256    /// Associate links with the ports of the given node in the given direction.
257    fn record_links(&mut self, node: Node, direction: Direction, links: &'a [table::LinkIndex]) {
258        let optype = self.hugr.get_optype(node);
259        // NOTE: `OpType::port_count` copies the signature, which significantly slows down the import.
260        debug_assert!(links.len() <= optype.port_count(direction));
261
262        for (link, port) in links.iter().zip(self.hugr.node_ports(node, direction)) {
263            self.link_ports
264                .entry((self.region_scope, *link))
265                .or_default()
266                .push((node, port));
267        }
268    }
269
270    /// Link up the ports in the hugr graph, according to the connectivity information that
271    /// has been gathered in the `link_ports` map.
272    fn link_ports(&mut self) -> Result<(), ImportError> {
273        // For each edge, we group the ports by their direction. We reuse the `inputs` and
274        // `outputs` vectors to avoid unnecessary allocations.
275        let mut inputs = Vec::new();
276        let mut outputs = Vec::new();
277
278        for (link_id, link_ports) in std::mem::take(&mut self.link_ports) {
279            // Skip the edge if it doesn't have any ports.
280            if link_ports.is_empty() {
281                continue;
282            }
283
284            for (node, port) in link_ports {
285                match port.as_directed() {
286                    Either::Left(input) => inputs.push((node, input)),
287                    Either::Right(output) => outputs.push((node, output)),
288                }
289            }
290
291            match (inputs.as_slice(), outputs.as_slice()) {
292                ([], []) => {
293                    unreachable!();
294                }
295                (_, [output]) => {
296                    for (node, port) in inputs.iter() {
297                        self.hugr.connect(output.0, output.1, *node, *port);
298                    }
299                }
300                ([input], _) => {
301                    for (node, port) in outputs.iter() {
302                        self.hugr.connect(*node, *port, input.0, input.1);
303                    }
304                }
305                _ => {
306                    return Err(error_unsupported!(
307                        "link {:?} would require hyperedge",
308                        link_id
309                    ));
310                }
311            }
312
313            inputs.clear();
314            outputs.clear();
315        }
316
317        Ok(())
318    }
319
320    fn link_static_ports(&mut self) -> Result<(), ImportError> {
321        for (src_id, dst_id) in std::mem::take(&mut self.static_edges) {
322            // None of these lookups should fail given how we constructed `static_edges`.
323            let src = self.nodes[&src_id];
324            let dst = self.nodes[&dst_id];
325            let src_port = self.hugr.get_optype(src).static_output_port().unwrap();
326            let dst_port = self.hugr.get_optype(dst).static_input_port().unwrap();
327            self.hugr.connect(src, src_port, dst, dst_port);
328        }
329
330        Ok(())
331    }
332
333    fn get_symbol_name(&self, node_id: table::NodeId) -> Result<&'a str, ImportError> {
334        let node_data = self.get_node(node_id)?;
335        let name = node_data
336            .operation
337            .symbol()
338            .ok_or(table::ModelError::InvalidSymbol(node_id))?;
339        Ok(name)
340    }
341
342    fn get_func_signature(
343        &mut self,
344        func_node: table::NodeId,
345    ) -> Result<PolyFuncType, ImportError> {
346        let symbol = match self.get_node(func_node)?.operation {
347            table::Operation::DefineFunc(symbol) => symbol,
348            table::Operation::DeclareFunc(symbol) => symbol,
349            _ => return Err(table::ModelError::UnexpectedOperation(func_node).into()),
350        };
351
352        self.import_poly_func_type(func_node, *symbol, |_, signature| Ok(signature))
353    }
354
355    /// Import the root region of the module.
356    fn import_root(&mut self) -> Result<(), ImportError> {
357        self.region_scope = self.module.root;
358        let region_data = self.get_region(self.module.root)?;
359
360        for node in region_data.children {
361            self.import_node(*node, self.hugr.entrypoint())?;
362        }
363
364        Ok(())
365    }
366
367    fn import_node(
368        &mut self,
369        node_id: table::NodeId,
370        parent: Node,
371    ) -> Result<Option<Node>, ImportError> {
372        let node_data = self.get_node(node_id)?;
373
374        match node_data.operation {
375            table::Operation::Invalid => Err(table::ModelError::InvalidOperation(node_id).into()),
376            table::Operation::Dfg => {
377                let signature = self.get_node_signature(node_id)?;
378                let optype = OpType::DFG(DFG { signature });
379                let node = self.make_node(node_id, optype, parent)?;
380
381                let [region] = node_data.regions else {
382                    return Err(table::ModelError::InvalidRegions(node_id).into());
383                };
384
385                self.import_dfg_region(node_id, *region, node)?;
386                Ok(Some(node))
387            }
388
389            table::Operation::Cfg => {
390                let signature = self.get_node_signature(node_id)?;
391                let optype = OpType::CFG(CFG { signature });
392                let node = self.make_node(node_id, optype, parent)?;
393
394                let [region] = node_data.regions else {
395                    return Err(table::ModelError::InvalidRegions(node_id).into());
396                };
397
398                self.import_cfg_region(node_id, *region, node)?;
399                Ok(Some(node))
400            }
401
402            table::Operation::Block => {
403                let node = self.import_cfg_block(node_id, parent)?;
404                Ok(Some(node))
405            }
406
407            table::Operation::DefineFunc(symbol) => {
408                self.import_poly_func_type(node_id, *symbol, |ctx, signature| {
409                    let optype = OpType::FuncDefn(FuncDefn {
410                        name: symbol.name.to_string(),
411                        signature,
412                    });
413
414                    let node = ctx.make_node(node_id, optype, parent)?;
415
416                    let [region] = node_data.regions else {
417                        return Err(table::ModelError::InvalidRegions(node_id).into());
418                    };
419
420                    ctx.import_dfg_region(node_id, *region, node)?;
421
422                    Ok(Some(node))
423                })
424            }
425
426            table::Operation::DeclareFunc(symbol) => {
427                self.import_poly_func_type(node_id, *symbol, |ctx, signature| {
428                    let optype = OpType::FuncDecl(FuncDecl {
429                        name: symbol.name.to_string(),
430                        signature,
431                    });
432
433                    let node = ctx.make_node(node_id, optype, parent)?;
434
435                    Ok(Some(node))
436                })
437            }
438
439            table::Operation::TailLoop => {
440                let node = self.import_tail_loop(node_id, parent)?;
441                Ok(Some(node))
442            }
443            table::Operation::Conditional => {
444                let node = self.import_conditional(node_id, parent)?;
445                Ok(Some(node))
446            }
447
448            table::Operation::Custom(operation) => {
449                if let Some([_, _]) = self.match_symbol(operation, model::CORE_CALL_INDIRECT)? {
450                    let signature = self.get_node_signature(node_id)?;
451                    let optype = OpType::CallIndirect(CallIndirect { signature });
452                    let node = self.make_node(node_id, optype, parent)?;
453                    return Ok(Some(node));
454                }
455
456                if let Some([_, _, func]) = self.match_symbol(operation, model::CORE_CALL)? {
457                    let table::Term::Apply(symbol, args) = self.get_term(func)? else {
458                        return Err(table::ModelError::TypeError(func).into());
459                    };
460
461                    let func_sig = self.get_func_signature(*symbol)?;
462
463                    let type_args = args
464                        .iter()
465                        .map(|term| self.import_type_arg(*term))
466                        .collect::<Result<Vec<TypeArg>, _>>()?;
467
468                    self.static_edges.push((*symbol, node_id));
469                    let optype = OpType::Call(Call::try_new(func_sig, type_args)?);
470
471                    let node = self.make_node(node_id, optype, parent)?;
472                    return Ok(Some(node));
473                }
474
475                if let Some([_, value]) = self.match_symbol(operation, model::CORE_LOAD_CONST)? {
476                    // If the constant refers directly to a function, import this as the `LoadFunc` operation.
477                    if let table::Term::Apply(symbol, args) = self.get_term(value)? {
478                        let func_node_data = self
479                            .module
480                            .get_node(*symbol)
481                            .ok_or(table::ModelError::NodeNotFound(*symbol))?;
482
483                        if let table::Operation::DefineFunc(_) | table::Operation::DeclareFunc(_) =
484                            func_node_data.operation
485                        {
486                            let func_sig = self.get_func_signature(*symbol)?;
487                            let type_args = args
488                                .iter()
489                                .map(|term| self.import_type_arg(*term))
490                                .collect::<Result<Vec<TypeArg>, _>>()?;
491
492                            self.static_edges.push((*symbol, node_id));
493
494                            let optype =
495                                OpType::LoadFunction(LoadFunction::try_new(func_sig, type_args)?);
496
497                            let node = self.make_node(node_id, optype, parent)?;
498                            return Ok(Some(node));
499                        }
500                    }
501
502                    // Otherwise use const nodes
503                    let signature = node_data
504                        .signature
505                        .ok_or_else(|| error_uninferred!("node signature"))?;
506                    let [_, outputs] = self.get_func_type(signature)?;
507                    let outputs = self.import_closed_list(outputs)?;
508                    let output = outputs
509                        .first()
510                        .ok_or(table::ModelError::TypeError(signature))?;
511                    let datatype = self.import_type(*output)?;
512
513                    let imported_value = self.import_value(value, *output)?;
514
515                    let load_const_node = self.make_node(
516                        node_id,
517                        OpType::LoadConstant(LoadConstant {
518                            datatype: datatype.clone(),
519                        }),
520                        parent,
521                    )?;
522
523                    let const_node = self
524                        .hugr
525                        .add_node_with_parent(parent, OpType::Const(Const::new(imported_value)));
526
527                    self.hugr.connect(const_node, 0, load_const_node, 0);
528
529                    return Ok(Some(load_const_node));
530                }
531
532                if let Some([_, _, tag]) = self.match_symbol(operation, model::CORE_MAKE_ADT)? {
533                    let table::Term::Literal(model::Literal::Nat(tag)) = self.get_term(tag)? else {
534                        return Err(table::ModelError::TypeError(tag).into());
535                    };
536
537                    let signature = node_data
538                        .signature
539                        .ok_or_else(|| error_uninferred!("node signature"))?;
540                    let [_, outputs] = self.get_func_type(signature)?;
541                    let (variants, _) = self.import_adt_and_rest(node_id, outputs)?;
542                    let node = self.make_node(
543                        node_id,
544                        OpType::Tag(Tag {
545                            variants,
546                            tag: *tag as usize,
547                        }),
548                        parent,
549                    )?;
550                    return Ok(Some(node));
551                }
552
553                let table::Term::Apply(node, params) = self.get_term(operation)? else {
554                    return Err(table::ModelError::TypeError(operation).into());
555                };
556                let name = self.get_symbol_name(*node)?;
557                let args = params
558                    .iter()
559                    .map(|param| self.import_type_arg(*param))
560                    .collect::<Result<Vec<_>, _>>()?;
561                let (extension, name) = self.import_custom_name(name)?;
562                let signature = self.get_node_signature(node_id)?;
563
564                // TODO: Currently we do not have the description or any other metadata for
565                // the custom op. This will improve with declarative extensions being able
566                // to declare operations as a node, in which case the description will be attached
567                // to that node as metadata.
568
569                let optype = OpType::OpaqueOp(OpaqueOp::new(
570                    extension,
571                    name,
572                    String::default(),
573                    args,
574                    signature,
575                ));
576
577                let node = self.make_node(node_id, optype, parent)?;
578
579                Ok(Some(node))
580            }
581
582            table::Operation::DefineAlias(symbol, value) => {
583                if !symbol.params.is_empty() {
584                    return Err(error_unsupported!(
585                        "parameters or constraints in alias definition"
586                    ));
587                }
588
589                let optype = OpType::AliasDefn(AliasDefn {
590                    name: symbol.name.to_smolstr(),
591                    definition: self.import_type(value)?,
592                });
593
594                let node = self.make_node(node_id, optype, parent)?;
595                Ok(Some(node))
596            }
597
598            table::Operation::DeclareAlias(symbol) => {
599                if !symbol.params.is_empty() {
600                    return Err(error_unsupported!(
601                        "parameters or constraints in alias declaration"
602                    ));
603                }
604
605                let optype = OpType::AliasDecl(AliasDecl {
606                    name: symbol.name.to_smolstr(),
607                    bound: TypeBound::Copyable,
608                });
609
610                let node = self.make_node(node_id, optype, parent)?;
611                Ok(Some(node))
612            }
613
614            table::Operation::Import { .. } => Ok(None),
615
616            table::Operation::DeclareConstructor { .. } => Ok(None),
617            table::Operation::DeclareOperation { .. } => Ok(None),
618        }
619    }
620
621    fn import_dfg_region(
622        &mut self,
623        node_id: table::NodeId,
624        region: table::RegionId,
625        node: Node,
626    ) -> Result<(), ImportError> {
627        let region_data = self.get_region(region)?;
628
629        let prev_region = self.region_scope;
630        if region_data.scope.is_some() {
631            self.region_scope = region;
632        }
633
634        if region_data.kind != model::RegionKind::DataFlow {
635            return Err(table::ModelError::InvalidRegions(node_id).into());
636        }
637
638        let signature = self.import_func_type(
639            region_data
640                .signature
641                .ok_or_else(|| error_uninferred!("region signature"))?,
642        )?;
643
644        // Create the input and output nodes
645        let input = self.hugr.add_node_with_parent(
646            node,
647            OpType::Input(Input {
648                types: signature.input,
649            }),
650        );
651        let output = self.hugr.add_node_with_parent(
652            node,
653            OpType::Output(Output {
654                types: signature.output,
655            }),
656        );
657
658        // Make sure that the ports of the input/output nodes are connected correctly
659        self.record_links(input, Direction::Outgoing, region_data.sources);
660        self.record_links(output, Direction::Incoming, region_data.targets);
661
662        for child in region_data.children {
663            self.import_node(*child, node)?;
664        }
665
666        self.create_order_edges(region)?;
667
668        for meta_item in region_data.meta {
669            self.import_node_metadata(node, *meta_item)?;
670        }
671
672        self.region_scope = prev_region;
673
674        Ok(())
675    }
676
677    /// Create order edges between nodes of a dataflow region based on order hint metadata.
678    ///
679    /// This method assumes that the nodes for the children of the region have already been imported.
680    fn create_order_edges(&mut self, region_id: table::RegionId) -> Result<(), ImportError> {
681        let region_data = self.get_region(region_id)?;
682        debug_assert_eq!(region_data.kind, model::RegionKind::DataFlow);
683
684        // Collect order hint keys
685        // PERFORMANCE: It might be worthwhile to reuse the map to avoid allocations.
686        let mut order_keys = FxHashMap::<u64, table::NodeId>::default();
687
688        for child_id in region_data.children {
689            let child_data = self.get_node(*child_id)?;
690
691            for meta_id in child_data.meta {
692                let Some([key]) = self.match_symbol(*meta_id, model::ORDER_HINT_KEY)? else {
693                    continue;
694                };
695
696                let table::Term::Literal(model::Literal::Nat(key)) = self.get_term(key)? else {
697                    continue;
698                };
699
700                if order_keys.insert(*key, *child_id).is_some() {
701                    return Err(OrderHintError::DuplicateKey(*child_id, *key).into());
702                }
703            }
704        }
705
706        // Insert order edges
707        for meta_id in region_data.meta {
708            let Some([a, b]) = self.match_symbol(*meta_id, model::ORDER_HINT_ORDER)? else {
709                continue;
710            };
711
712            let table::Term::Literal(model::Literal::Nat(a)) = self.get_term(a)? else {
713                continue;
714            };
715
716            let table::Term::Literal(model::Literal::Nat(b)) = self.get_term(b)? else {
717                continue;
718            };
719
720            let a = order_keys.get(a).ok_or(OrderHintError::UnknownKey(*a))?;
721            let b = order_keys.get(b).ok_or(OrderHintError::UnknownKey(*b))?;
722
723            // NOTE: The lookups here are expected to succeed since we only
724            // process the order metadata after we have imported the nodes.
725            let a_node = self.nodes[a];
726            let b_node = self.nodes[b];
727
728            let a_port = self
729                .hugr
730                .get_optype(a_node)
731                .other_output_port()
732                .ok_or(OrderHintError::NoOrderPort(*a))?;
733
734            let b_port = self
735                .hugr
736                .get_optype(b_node)
737                .other_input_port()
738                .ok_or(OrderHintError::NoOrderPort(*b))?;
739
740            self.hugr.connect(a_node, a_port, b_node, b_port);
741        }
742
743        Ok(())
744    }
745
746    fn import_adt_and_rest(
747        &mut self,
748        node_id: table::NodeId,
749        list: table::TermId,
750    ) -> Result<(Vec<TypeRow>, TypeRow), ImportError> {
751        let items = self.import_closed_list(list)?;
752
753        let Some((first, rest)) = items.split_first() else {
754            return Err(table::ModelError::InvalidRegions(node_id).into());
755        };
756
757        let sum_rows: Vec<_> = {
758            let [variants] = self.expect_symbol(*first, model::CORE_ADT)?;
759            self.import_type_rows(variants)?
760        };
761
762        let rest = rest
763            .iter()
764            .map(|term| self.import_type(*term))
765            .collect::<Result<Vec<_>, _>>()?
766            .into();
767
768        Ok((sum_rows, rest))
769    }
770
771    fn import_tail_loop(
772        &mut self,
773        node_id: table::NodeId,
774        parent: Node,
775    ) -> Result<Node, ImportError> {
776        let node_data = self.get_node(node_id)?;
777        debug_assert_eq!(node_data.operation, table::Operation::TailLoop);
778
779        let [region] = node_data.regions else {
780            return Err(table::ModelError::InvalidRegions(node_id).into());
781        };
782        let region_data = self.get_region(*region)?;
783
784        let [_, region_outputs] = self.get_func_type(
785            region_data
786                .signature
787                .ok_or_else(|| error_uninferred!("region signature"))?,
788        )?;
789        let (sum_rows, rest) = self.import_adt_and_rest(node_id, region_outputs)?;
790
791        let (just_inputs, just_outputs) = {
792            let mut sum_rows = sum_rows.into_iter();
793
794            let Some(just_inputs) = sum_rows.next() else {
795                return Err(table::ModelError::TypeError(region_outputs).into());
796            };
797
798            let Some(just_outputs) = sum_rows.next() else {
799                return Err(table::ModelError::TypeError(region_outputs).into());
800            };
801
802            (just_inputs, just_outputs)
803        };
804
805        let optype = OpType::TailLoop(TailLoop {
806            just_inputs,
807            just_outputs,
808            rest,
809        });
810
811        let node = self.make_node(node_id, optype, parent)?;
812
813        self.import_dfg_region(node_id, *region, node)?;
814        Ok(node)
815    }
816
817    fn import_conditional(
818        &mut self,
819        node_id: table::NodeId,
820        parent: Node,
821    ) -> Result<Node, ImportError> {
822        let node_data = self.get_node(node_id)?;
823        debug_assert_eq!(node_data.operation, table::Operation::Conditional);
824        let [inputs, outputs] = self.get_func_type(
825            node_data
826                .signature
827                .ok_or_else(|| error_uninferred!("node signature"))?,
828        )?;
829        let (sum_rows, other_inputs) = self.import_adt_and_rest(node_id, inputs)?;
830        let outputs = self.import_type_row(outputs)?;
831
832        let optype = OpType::Conditional(Conditional {
833            sum_rows,
834            other_inputs,
835            outputs,
836        });
837
838        let node = self.make_node(node_id, optype, parent)?;
839
840        for region in node_data.regions {
841            let region_data = self.get_region(*region)?;
842            let signature = self.import_func_type(
843                region_data
844                    .signature
845                    .ok_or_else(|| error_uninferred!("region signature"))?,
846            )?;
847
848            let case_node = self
849                .hugr
850                .add_node_with_parent(node, OpType::Case(Case { signature }));
851
852            self.import_dfg_region(node_id, *region, case_node)?;
853        }
854
855        Ok(node)
856    }
857
858    fn import_cfg_region(
859        &mut self,
860        node_id: table::NodeId,
861        region: table::RegionId,
862        node: Node,
863    ) -> Result<(), ImportError> {
864        let region_data = self.get_region(region)?;
865
866        if region_data.kind != model::RegionKind::ControlFlow {
867            return Err(table::ModelError::InvalidRegions(node_id).into());
868        }
869
870        let prev_region = self.region_scope;
871        if region_data.scope.is_some() {
872            self.region_scope = region;
873        }
874
875        let [region_source, region_targets] = self.get_func_type(
876            region_data
877                .signature
878                .ok_or_else(|| error_uninferred!("region signature"))?,
879        )?;
880
881        let region_source_types = self.import_closed_list(region_source)?;
882        let region_target_types = self.import_closed_list(region_targets)?;
883
884        // Create the entry node for the control flow region.
885        // Since the core hugr does not have explicit entry blocks yet, we create a dataflow block
886        // that simply forwards its inputs to its outputs.
887        {
888            let types = {
889                let [ctrl_type] = region_source_types.as_slice() else {
890                    return Err(table::ModelError::TypeError(region_source).into());
891                };
892
893                let [types] = self.expect_symbol(*ctrl_type, model::CORE_CTRL)?;
894                self.import_type_row(types)?
895            };
896
897            let entry = self.hugr.add_node_with_parent(
898                node,
899                OpType::DataflowBlock(DataflowBlock {
900                    inputs: types.clone(),
901                    other_outputs: TypeRow::default(),
902                    sum_rows: vec![types.clone()],
903                }),
904            );
905
906            self.record_links(entry, Direction::Outgoing, region_data.sources);
907
908            let node_input = self.hugr.add_node_with_parent(
909                entry,
910                OpType::Input(Input {
911                    types: types.clone(),
912                }),
913            );
914
915            let node_output = self.hugr.add_node_with_parent(
916                entry,
917                OpType::Output(Output {
918                    types: vec![Type::new_sum([types.clone()])].into(),
919                }),
920            );
921
922            let node_tag = self.hugr.add_node_with_parent(
923                entry,
924                OpType::Tag(Tag {
925                    tag: 0,
926                    variants: vec![types],
927                }),
928            );
929
930            // Connect the input node to the tag node
931            let input_outputs = self.hugr.node_outputs(node_input);
932            let tag_inputs = self.hugr.node_inputs(node_tag);
933            let mut connections =
934                Vec::with_capacity(input_outputs.size_hint().0 + tag_inputs.size_hint().0);
935
936            for (a, b) in input_outputs.zip(tag_inputs) {
937                connections.push((node_input, a, node_tag, b));
938            }
939
940            // Connect the tag node to the output node
941            let tag_outputs = self.hugr.node_outputs(node_tag);
942            let output_inputs = self.hugr.node_inputs(node_output);
943
944            for (a, b) in tag_outputs.zip(output_inputs) {
945                connections.push((node_tag, a, node_output, b));
946            }
947
948            for (src, src_port, dst, dst_port) in connections {
949                self.hugr.connect(src, src_port, dst, dst_port);
950            }
951        }
952
953        for child in region_data.children {
954            self.import_node(*child, node)?;
955        }
956
957        // Create the exit node for the control flow region.
958        {
959            let cfg_outputs = {
960                let [ctrl_type] = region_target_types.as_slice() else {
961                    return Err(table::ModelError::TypeError(region_targets).into());
962                };
963
964                let [types] = self.expect_symbol(*ctrl_type, model::CORE_CTRL)?;
965                self.import_type_row(types)?
966            };
967
968            let exit = self
969                .hugr
970                .add_node_with_parent(node, OpType::ExitBlock(ExitBlock { cfg_outputs }));
971            self.record_links(exit, Direction::Incoming, region_data.targets);
972        }
973
974        for meta_item in region_data.meta {
975            self.import_node_metadata(node, *meta_item)?;
976        }
977
978        self.region_scope = prev_region;
979
980        Ok(())
981    }
982
983    fn import_cfg_block(
984        &mut self,
985        node_id: table::NodeId,
986        parent: Node,
987    ) -> Result<Node, ImportError> {
988        let node_data = self.get_node(node_id)?;
989        debug_assert_eq!(node_data.operation, table::Operation::Block);
990
991        let [region] = node_data.regions else {
992            return Err(table::ModelError::InvalidRegions(node_id).into());
993        };
994        let region_data = self.get_region(*region)?;
995        let [inputs, outputs] = self.get_func_type(
996            region_data
997                .signature
998                .ok_or_else(|| error_uninferred!("region signature"))?,
999        )?;
1000        let inputs = self.import_type_row(inputs)?;
1001        let (sum_rows, other_outputs) = self.import_adt_and_rest(node_id, outputs)?;
1002
1003        let optype = OpType::DataflowBlock(DataflowBlock {
1004            inputs,
1005            other_outputs,
1006            sum_rows,
1007        });
1008        let node = self.make_node(node_id, optype, parent)?;
1009
1010        self.import_dfg_region(node_id, *region, node)?;
1011        Ok(node)
1012    }
1013
1014    fn import_poly_func_type<RV: MaybeRV, T>(
1015        &mut self,
1016        node: table::NodeId,
1017        symbol: table::Symbol<'a>,
1018        in_scope: impl FnOnce(&mut Self, PolyFuncTypeBase<RV>) -> Result<T, ImportError>,
1019    ) -> Result<T, ImportError> {
1020        let mut imported_params = Vec::with_capacity(symbol.params.len());
1021
1022        for (index, param) in symbol.params.iter().enumerate() {
1023            self.local_vars
1024                .insert(table::VarId(node, index as _), LocalVar::new(param.r#type));
1025        }
1026
1027        for constraint in symbol.constraints {
1028            if let Some([term]) = self.match_symbol(*constraint, model::CORE_NON_LINEAR)? {
1029                let table::Term::Var(var) = self.get_term(term)? else {
1030                    return Err(error_unsupported!(
1031                        "constraint on term that is not a variable"
1032                    ));
1033                };
1034
1035                self.local_vars
1036                    .get_mut(var)
1037                    .ok_or(table::ModelError::InvalidVar(*var))?
1038                    .bound = TypeBound::Copyable;
1039            } else {
1040                return Err(error_unsupported!("constraint other than copy or discard"));
1041            }
1042        }
1043
1044        for (index, param) in symbol.params.iter().enumerate() {
1045            // NOTE: `PolyFuncType` only has explicit type parameters at present.
1046            let bound = self.local_vars[&table::VarId(node, index as _)].bound;
1047            imported_params.push(self.import_type_param(param.r#type, bound)?);
1048        }
1049
1050        let body = self.import_func_type::<RV>(symbol.signature)?;
1051        in_scope(self, PolyFuncTypeBase::new(imported_params, body))
1052    }
1053
1054    /// Import a [`TypeParam`] from a term that represents a static type.
1055    fn import_type_param(
1056        &mut self,
1057        term_id: table::TermId,
1058        bound: TypeBound,
1059    ) -> Result<TypeParam, ImportError> {
1060        if let Some([]) = self.match_symbol(term_id, model::CORE_STR_TYPE)? {
1061            return Ok(TypeParam::String);
1062        }
1063
1064        if let Some([]) = self.match_symbol(term_id, model::CORE_NAT_TYPE)? {
1065            return Ok(TypeParam::max_nat());
1066        }
1067
1068        if let Some([]) = self.match_symbol(term_id, model::CORE_BYTES_TYPE)? {
1069            return Err(error_unsupported!(
1070                "`{}` as `TypeParam`",
1071                model::CORE_BYTES_TYPE
1072            ));
1073        }
1074
1075        if let Some([]) = self.match_symbol(term_id, model::CORE_FLOAT_TYPE)? {
1076            return Err(error_unsupported!(
1077                "`{}` as `TypeParam`",
1078                model::CORE_FLOAT_TYPE
1079            ));
1080        }
1081
1082        if let Some([]) = self.match_symbol(term_id, model::CORE_TYPE)? {
1083            return Ok(TypeParam::Type { b: bound });
1084        }
1085
1086        if let Some([]) = self.match_symbol(term_id, model::CORE_STATIC)? {
1087            return Err(error_unsupported!(
1088                "`{}` as `TypeParam`",
1089                model::CORE_STATIC
1090            ));
1091        }
1092
1093        if let Some([]) = self.match_symbol(term_id, model::CORE_CONSTRAINT)? {
1094            return Err(error_unsupported!(
1095                "`{}` as `TypeParam`",
1096                model::CORE_CONSTRAINT
1097            ));
1098        }
1099
1100        if let Some([]) = self.match_symbol(term_id, model::CORE_CONST)? {
1101            return Err(error_unsupported!("`{}` as `TypeParam`", model::CORE_CONST));
1102        }
1103
1104        if let Some([]) = self.match_symbol(term_id, model::CORE_CTRL_TYPE)? {
1105            return Err(error_unsupported!(
1106                "`{}` as `TypeParam`",
1107                model::CORE_CTRL_TYPE
1108            ));
1109        }
1110
1111        if let Some([item_type]) = self.match_symbol(term_id, model::CORE_LIST_TYPE)? {
1112            // At present `hugr-model` has no way to express that the item
1113            // type of a list must be copyable. Therefore we import it as `Any`.
1114            let param = Box::new(self.import_type_param(item_type, TypeBound::Any)?);
1115            return Ok(TypeParam::List { param });
1116        }
1117
1118        if let Some([_]) = self.match_symbol(term_id, model::CORE_TUPLE_TYPE)? {
1119            // At present `hugr-model` has no way to express that the item
1120            // types of a tuple must be copyable. Therefore we import it as `Any`.
1121            todo!("import tuple type");
1122        }
1123
1124        match self.get_term(term_id)? {
1125            table::Term::Wildcard => Err(error_uninferred!("wildcard")),
1126
1127            table::Term::Var { .. } => Err(error_unsupported!("type variable as `TypeParam`")),
1128            table::Term::Apply(symbol, _) => {
1129                let name = self.get_symbol_name(*symbol)?;
1130                Err(error_unsupported!("custom type `{}` as `TypeParam`", name))
1131            }
1132
1133            table::Term::Tuple(_)
1134            | table::Term::List { .. }
1135            | table::Term::Func { .. }
1136            | table::Term::Literal(_) => Err(table::ModelError::TypeError(term_id).into()),
1137        }
1138    }
1139
1140    /// Import a `TypeArg` from a term that represents a static type or value.
1141    fn import_type_arg(&mut self, term_id: table::TermId) -> Result<TypeArg, ImportError> {
1142        if let Some([]) = self.match_symbol(term_id, model::CORE_STR_TYPE)? {
1143            return Err(error_unsupported!(
1144                "`{}` as `TypeArg`",
1145                model::CORE_STR_TYPE
1146            ));
1147        }
1148
1149        if let Some([]) = self.match_symbol(term_id, model::CORE_NAT_TYPE)? {
1150            return Err(error_unsupported!(
1151                "`{}` as `TypeArg`",
1152                model::CORE_NAT_TYPE
1153            ));
1154        }
1155
1156        if let Some([]) = self.match_symbol(term_id, model::CORE_BYTES_TYPE)? {
1157            return Err(error_unsupported!(
1158                "`{}` as `TypeArg`",
1159                model::CORE_BYTES_TYPE
1160            ));
1161        }
1162
1163        if let Some([]) = self.match_symbol(term_id, model::CORE_FLOAT_TYPE)? {
1164            return Err(error_unsupported!(
1165                "`{}` as `TypeArg`",
1166                model::CORE_FLOAT_TYPE
1167            ));
1168        }
1169
1170        if let Some([]) = self.match_symbol(term_id, model::CORE_TYPE)? {
1171            return Err(error_unsupported!("`{}` as `TypeArg`", model::CORE_TYPE));
1172        }
1173
1174        if let Some([]) = self.match_symbol(term_id, model::CORE_CONSTRAINT)? {
1175            return Err(error_unsupported!(
1176                "`{}` as `TypeArg`",
1177                model::CORE_CONSTRAINT
1178            ));
1179        }
1180
1181        if let Some([]) = self.match_symbol(term_id, model::CORE_STATIC)? {
1182            return Err(error_unsupported!("`{}` as `TypeArg`", model::CORE_STATIC));
1183        }
1184
1185        if let Some([]) = self.match_symbol(term_id, model::CORE_CTRL_TYPE)? {
1186            return Err(error_unsupported!(
1187                "`{}` as `TypeArg`",
1188                model::CORE_CTRL_TYPE
1189            ));
1190        }
1191
1192        if let Some([]) = self.match_symbol(term_id, model::CORE_CONST)? {
1193            return Err(error_unsupported!("`{}` as `TypeArg`", model::CORE_CONST));
1194        }
1195
1196        if let Some([]) = self.match_symbol(term_id, model::CORE_LIST_TYPE)? {
1197            return Err(error_unsupported!(
1198                "`{}` as `TypeArg`",
1199                model::CORE_LIST_TYPE
1200            ));
1201        }
1202
1203        match self.get_term(term_id)? {
1204            table::Term::Wildcard => Err(error_uninferred!("wildcard")),
1205
1206            table::Term::Var(var) => {
1207                let var_info = self
1208                    .local_vars
1209                    .get(var)
1210                    .ok_or(table::ModelError::InvalidVar(*var))?;
1211                let decl = self.import_type_param(var_info.r#type, var_info.bound)?;
1212                Ok(TypeArg::new_var_use(var.1 as _, decl))
1213            }
1214
1215            table::Term::List { .. } => {
1216                let elems = self
1217                    .import_closed_list(term_id)?
1218                    .iter()
1219                    .map(|item| self.import_type_arg(*item))
1220                    .collect::<Result<_, _>>()?;
1221
1222                Ok(TypeArg::Sequence { elems })
1223            }
1224
1225            table::Term::Tuple { .. } => {
1226                // NOTE: While `TypeArg`s can represent tuples as
1227                // `TypeArg::Sequence`s, this conflates lists and tuples. To
1228                // avoid ambiguity we therefore report an error here for now.
1229                Err(error_unsupported!("tuples as `TypeArg`"))
1230            }
1231
1232            table::Term::Literal(model::Literal::Str(value)) => Ok(TypeArg::String {
1233                arg: value.to_string(),
1234            }),
1235
1236            table::Term::Literal(model::Literal::Nat(value)) => {
1237                Ok(TypeArg::BoundedNat { n: *value })
1238            }
1239
1240            table::Term::Literal(model::Literal::Bytes(_)) => {
1241                Err(error_unsupported!("`(bytes ..)` as `TypeArg`"))
1242            }
1243            table::Term::Literal(model::Literal::Float(_)) => {
1244                Err(error_unsupported!("float literal as `TypeArg`"))
1245            }
1246            table::Term::Func { .. } => Err(error_unsupported!("function constant as `TypeArg`")),
1247
1248            table::Term::Apply { .. } => {
1249                let ty = self.import_type(term_id)?;
1250                Ok(TypeArg::Type { ty })
1251            }
1252        }
1253    }
1254
1255    /// Import a `Type` from a term that represents a runtime type.
1256    fn import_type<RV: MaybeRV>(
1257        &mut self,
1258        term_id: table::TermId,
1259    ) -> Result<TypeBase<RV>, ImportError> {
1260        if let Some([_, _]) = self.match_symbol(term_id, model::CORE_FN)? {
1261            let func_type = self.import_func_type::<RowVariable>(term_id)?;
1262            return Ok(TypeBase::new_function(func_type));
1263        }
1264
1265        if let Some([variants]) = self.match_symbol(term_id, model::CORE_ADT)? {
1266            let variants = self.import_closed_list(variants)?;
1267            let variants = variants
1268                .iter()
1269                .map(|variant| self.import_type_row::<RowVariable>(*variant))
1270                .collect::<Result<Vec<_>, _>>()?;
1271            return Ok(TypeBase::new_sum(variants));
1272        }
1273
1274        match self.get_term(term_id)? {
1275            table::Term::Wildcard => Err(error_uninferred!("wildcard")),
1276
1277            table::Term::Apply(symbol, args) => {
1278                let args = args
1279                    .iter()
1280                    .map(|arg| self.import_type_arg(*arg))
1281                    .collect::<Result<Vec<_>, _>>()?;
1282
1283                let name = self.get_symbol_name(*symbol)?;
1284                let (extension, id) = self.import_custom_name(name)?;
1285
1286                let extension_ref =
1287                    self.extensions
1288                        .get(&extension)
1289                        .ok_or_else(|| ImportError::Extension {
1290                            missing_ext: extension.clone(),
1291                            available: self.extensions.ids().cloned().collect(),
1292                        })?;
1293
1294                let ext_type =
1295                    extension_ref
1296                        .get_type(&id)
1297                        .ok_or_else(|| ImportError::ExtensionType {
1298                            ext: extension.clone(),
1299                            name: id.clone(),
1300                        })?;
1301
1302                let bound = ext_type.bound(&args);
1303
1304                Ok(TypeBase::new_extension(CustomType::new(
1305                    id,
1306                    args,
1307                    extension,
1308                    bound,
1309                    &Arc::downgrade(extension_ref),
1310                )))
1311            }
1312
1313            table::Term::Var(table::VarId(_, index)) => {
1314                Ok(TypeBase::new_var_use(*index as _, TypeBound::Copyable))
1315            }
1316
1317            // The following terms are not runtime types, but the core `Type` only contains runtime types.
1318            // We therefore report a type error here.
1319            table::Term::List { .. }
1320            | table::Term::Tuple { .. }
1321            | table::Term::Literal(_)
1322            | table::Term::Func { .. } => Err(table::ModelError::TypeError(term_id).into()),
1323        }
1324    }
1325
1326    fn get_func_type(&mut self, term_id: table::TermId) -> Result<[table::TermId; 2], ImportError> {
1327        self.match_symbol(term_id, model::CORE_FN)?
1328            .ok_or(table::ModelError::TypeError(term_id).into())
1329    }
1330
1331    fn import_func_type<RV: MaybeRV>(
1332        &mut self,
1333        term_id: table::TermId,
1334    ) -> Result<FuncTypeBase<RV>, ImportError> {
1335        let [inputs, outputs] = self.get_func_type(term_id)?;
1336        let inputs = self.import_type_row(inputs)?;
1337        let outputs = self.import_type_row(outputs)?;
1338        Ok(FuncTypeBase::new(inputs, outputs))
1339    }
1340
1341    fn import_closed_list(
1342        &mut self,
1343        term_id: table::TermId,
1344    ) -> Result<Vec<table::TermId>, ImportError> {
1345        fn import_into(
1346            ctx: &mut Context,
1347            term_id: table::TermId,
1348            types: &mut Vec<table::TermId>,
1349        ) -> Result<(), ImportError> {
1350            match ctx.get_term(term_id)? {
1351                table::Term::List(parts) => {
1352                    types.reserve(parts.len());
1353
1354                    for part in *parts {
1355                        match part {
1356                            table::SeqPart::Item(term_id) => {
1357                                types.push(*term_id);
1358                            }
1359                            table::SeqPart::Splice(term_id) => {
1360                                import_into(ctx, *term_id, types)?;
1361                            }
1362                        }
1363                    }
1364                }
1365                _ => return Err(table::ModelError::TypeError(term_id).into()),
1366            }
1367
1368            Ok(())
1369        }
1370
1371        let mut types = Vec::new();
1372        import_into(self, term_id, &mut types)?;
1373        Ok(types)
1374    }
1375
1376    fn import_closed_tuple(
1377        &mut self,
1378        term_id: table::TermId,
1379    ) -> Result<Vec<table::TermId>, ImportError> {
1380        fn import_into(
1381            ctx: &mut Context,
1382            term_id: table::TermId,
1383            types: &mut Vec<table::TermId>,
1384        ) -> Result<(), ImportError> {
1385            match ctx.get_term(term_id)? {
1386                table::Term::Tuple(parts) => {
1387                    types.reserve(parts.len());
1388
1389                    for part in *parts {
1390                        match part {
1391                            table::SeqPart::Item(term_id) => {
1392                                types.push(*term_id);
1393                            }
1394                            table::SeqPart::Splice(term_id) => {
1395                                import_into(ctx, *term_id, types)?;
1396                            }
1397                        }
1398                    }
1399                }
1400                _ => return Err(table::ModelError::TypeError(term_id).into()),
1401            }
1402
1403            Ok(())
1404        }
1405
1406        let mut types = Vec::new();
1407        import_into(self, term_id, &mut types)?;
1408        Ok(types)
1409    }
1410
1411    fn import_type_rows<RV: MaybeRV>(
1412        &mut self,
1413        term_id: table::TermId,
1414    ) -> Result<Vec<TypeRowBase<RV>>, ImportError> {
1415        self.import_closed_list(term_id)?
1416            .into_iter()
1417            .map(|term_id| self.import_type_row::<RV>(term_id))
1418            .collect()
1419    }
1420
1421    fn import_type_row<RV: MaybeRV>(
1422        &mut self,
1423        term_id: table::TermId,
1424    ) -> Result<TypeRowBase<RV>, ImportError> {
1425        fn import_into<RV: MaybeRV>(
1426            ctx: &mut Context,
1427            term_id: table::TermId,
1428            types: &mut Vec<TypeBase<RV>>,
1429        ) -> Result<(), ImportError> {
1430            match ctx.get_term(term_id)? {
1431                table::Term::List(parts) => {
1432                    types.reserve(parts.len());
1433
1434                    for item in *parts {
1435                        match item {
1436                            table::SeqPart::Item(term_id) => {
1437                                types.push(ctx.import_type::<RV>(*term_id)?);
1438                            }
1439                            table::SeqPart::Splice(term_id) => {
1440                                import_into(ctx, *term_id, types)?;
1441                            }
1442                        }
1443                    }
1444                }
1445                table::Term::Var(table::VarId(_, index)) => {
1446                    let var = RV::try_from_rv(RowVariable(*index as _, TypeBound::Any))
1447                        .map_err(|_| table::ModelError::TypeError(term_id))?;
1448                    types.push(TypeBase::new(TypeEnum::RowVar(var)));
1449                }
1450                _ => return Err(table::ModelError::TypeError(term_id).into()),
1451            }
1452
1453            Ok(())
1454        }
1455
1456        let mut types = Vec::new();
1457        import_into(self, term_id, &mut types)?;
1458        Ok(types.into())
1459    }
1460
1461    fn import_custom_name(
1462        &mut self,
1463        symbol: &'a str,
1464    ) -> Result<(ExtensionId, SmolStr), ImportError> {
1465        use std::collections::hash_map::Entry;
1466        match self.custom_name_cache.entry(symbol) {
1467            Entry::Occupied(occupied_entry) => Ok(occupied_entry.get().clone()),
1468            Entry::Vacant(vacant_entry) => {
1469                let qualified_name = ExtensionId::new(symbol)
1470                    .map_err(|_| table::ModelError::MalformedName(symbol.to_smolstr()))?;
1471
1472                let (extension, id) = qualified_name
1473                    .split_last()
1474                    .ok_or_else(|| table::ModelError::MalformedName(symbol.to_smolstr()))?;
1475
1476                vacant_entry.insert((extension.clone(), id.clone()));
1477                Ok((extension, id))
1478            }
1479        }
1480    }
1481
1482    fn import_value(
1483        &mut self,
1484        term_id: table::TermId,
1485        type_id: table::TermId,
1486    ) -> Result<Value, ImportError> {
1487        let term_data = self.get_term(term_id)?;
1488
1489        // NOTE: We have special cased arrays, integers, and floats for now.
1490        // TODO: Allow arbitrary extension values to be imported from terms.
1491
1492        if let Some([runtime_type, json]) = self.match_symbol(term_id, model::COMPAT_CONST_JSON)? {
1493            let table::Term::Literal(model::Literal::Str(json)) = self.get_term(json)? else {
1494                return Err(table::ModelError::TypeError(term_id).into());
1495            };
1496
1497            // We attempt to deserialize as the custom const directly.
1498            // This might fail due to the custom const struct not being included when
1499            // this code was compiled; in that case, we fall back to the serialized form.
1500            let value: Option<Box<dyn CustomConst>> = serde_json::from_str(json).ok();
1501
1502            if let Some(value) = value {
1503                let opaque_value = OpaqueValue::from(value);
1504                return Ok(Value::Extension { e: opaque_value });
1505            } else {
1506                let runtime_type = self.import_type(runtime_type)?;
1507                let value: serde_json::Value = serde_json::from_str(json)
1508                    .map_err(|_| table::ModelError::TypeError(term_id))?;
1509                let custom_const = CustomSerialized::new(runtime_type, value);
1510                let opaque_value = OpaqueValue::new(custom_const);
1511                return Ok(Value::Extension { e: opaque_value });
1512            }
1513        }
1514
1515        if let Some([_, element_type_term, contents]) =
1516            self.match_symbol(term_id, ArrayValue::CTR_NAME)?
1517        {
1518            let element_type = self.import_type(element_type_term)?;
1519            let contents = self.import_closed_list(contents)?;
1520            let contents = contents
1521                .iter()
1522                .map(|item| self.import_value(*item, element_type_term))
1523                .collect::<Result<Vec<_>, _>>()?;
1524            return Ok(ArrayValue::new(element_type, contents).into());
1525        }
1526
1527        if let Some([bitwidth, value]) = self.match_symbol(term_id, ConstInt::CTR_NAME)? {
1528            let bitwidth = {
1529                let table::Term::Literal(model::Literal::Nat(bitwidth)) =
1530                    self.get_term(bitwidth)?
1531                else {
1532                    return Err(table::ModelError::TypeError(term_id).into());
1533                };
1534                if *bitwidth > 6 {
1535                    return Err(table::ModelError::TypeError(term_id).into());
1536                }
1537                *bitwidth as u8
1538            };
1539
1540            let value = {
1541                let table::Term::Literal(model::Literal::Nat(value)) = self.get_term(value)? else {
1542                    return Err(table::ModelError::TypeError(term_id).into());
1543                };
1544                *value
1545            };
1546
1547            return Ok(ConstInt::new_u(bitwidth, value)
1548                .map_err(|_| table::ModelError::TypeError(term_id))?
1549                .into());
1550        }
1551
1552        if let Some([value]) = self.match_symbol(term_id, ConstF64::CTR_NAME)? {
1553            let table::Term::Literal(model::Literal::Float(value)) = self.get_term(value)? else {
1554                return Err(table::ModelError::TypeError(term_id).into());
1555            };
1556
1557            return Ok(ConstF64::new(value.into_inner()).into());
1558        }
1559
1560        if let Some([_, _, tag, values]) = self.match_symbol(term_id, model::CORE_CONST_ADT)? {
1561            let [variants] = self.expect_symbol(type_id, model::CORE_ADT)?;
1562            let values = self.import_closed_tuple(values)?;
1563            let variants = self.import_closed_list(variants)?;
1564
1565            let table::Term::Literal(model::Literal::Nat(tag)) = self.get_term(tag)? else {
1566                return Err(table::ModelError::TypeError(term_id).into());
1567            };
1568
1569            let variant = variants
1570                .get(*tag as usize)
1571                .ok_or(table::ModelError::TypeError(term_id))?;
1572
1573            let variant = self.import_closed_list(*variant)?;
1574
1575            let items = values
1576                .iter()
1577                .zip(variant.iter())
1578                .map(|(value, typ)| self.import_value(*value, *typ))
1579                .collect::<Result<Vec<_>, _>>()?;
1580
1581            let typ = {
1582                // TODO: Import as a `SumType` directly and avoid the copy.
1583                let typ: Type = self.import_type(type_id)?;
1584                match typ.as_type_enum() {
1585                    TypeEnum::Sum(sum) => sum.clone(),
1586                    _ => unreachable!(),
1587                }
1588            };
1589
1590            return Ok(Value::sum(*tag as _, items, typ).unwrap());
1591        }
1592
1593        match term_data {
1594            table::Term::Wildcard => Err(error_uninferred!("wildcard")),
1595            table::Term::Var(_) => Err(error_unsupported!("constant value containing a variable")),
1596
1597            table::Term::Apply(symbol, _) => {
1598                let symbol_name = self.get_symbol_name(*symbol)?;
1599                Err(error_unsupported!(
1600                    "unknown custom constant value `{}`",
1601                    symbol_name
1602                ))
1603                // TODO: This should ultimately include the following cases:
1604                // - function definitions
1605                // - custom constructors for values
1606            }
1607
1608            table::Term::List { .. } | table::Term::Tuple(_) | table::Term::Literal(_) => {
1609                Err(table::ModelError::TypeError(term_id).into())
1610            }
1611
1612            table::Term::Func { .. } => Err(error_unsupported!("constant function value")),
1613        }
1614    }
1615
1616    fn match_symbol<const N: usize>(
1617        &self,
1618        term_id: table::TermId,
1619        name: &str,
1620    ) -> Result<Option<[table::TermId; N]>, ImportError> {
1621        let term = self.get_term(term_id)?;
1622
1623        // TODO: Follow alias chains?
1624
1625        let table::Term::Apply(symbol, args) = term else {
1626            return Ok(None);
1627        };
1628
1629        if name != self.get_symbol_name(*symbol)? {
1630            return Ok(None);
1631        }
1632
1633        Ok((*args).try_into().ok())
1634    }
1635
1636    fn expect_symbol<const N: usize>(
1637        &self,
1638        term_id: table::TermId,
1639        name: &str,
1640    ) -> Result<[table::TermId; N], ImportError> {
1641        self.match_symbol(term_id, name)?
1642            .ok_or(table::ModelError::TypeError(term_id).into())
1643    }
1644}
1645
1646/// Information about a local variable.
1647#[derive(Debug, Clone, Copy)]
1648struct LocalVar {
1649    /// The type of the variable.
1650    r#type: table::TermId,
1651    /// The type bound of the variable.
1652    bound: TypeBound,
1653}
1654
1655impl LocalVar {
1656    pub fn new(r#type: table::TermId) -> Self {
1657        Self {
1658            r#type,
1659            bound: TypeBound::Any,
1660        }
1661    }
1662}