egglog/
lib.rs

1#![doc = include_str!("lib.md")]
2pub mod api;
3pub mod ast;
4#[cfg(feature = "bin")]
5mod cli;
6mod command_macro;
7pub mod constraint;
8mod core;
9mod exec_state;
10pub mod extract;
11pub mod prelude;
12mod proofs;
13
14pub mod scheduler;
15mod serialize;
16pub mod sort;
17mod termdag;
18mod typechecking;
19pub mod util;
20pub use command_macro::{CommandMacro, CommandMacroRegistry};
21
22// This is used to allow the `add_primitive` macro to work in
23// both this crate and other crates by referring to `::egglog`.
24extern crate self as egglog;
25pub use ast::{ResolvedExpr, ResolvedFact, ResolvedVar};
26#[cfg(feature = "bin")]
27pub use cli::*;
28use constraint::{Constraint, Problem, SimpleTypeConstraint, TypeConstraint};
29use core::CoreActionContext;
30use core::ResolvedAtomTerm;
31pub use core::{Atom, AtomTerm};
32pub use core::{ResolvedCall, SpecializedPrimitive};
33pub use core_relations::{BaseValue, ContainerValue, Value};
34use core_relations::{ExecutionState, ExternalFunctionId, make_external_func};
35use csv::Writer;
36pub use egglog_add_primitive::add_literal_prim;
37pub use egglog_add_primitive::add_primitive;
38pub use egglog_add_primitive::add_primitive_with_validator;
39use egglog_ast::generic_ast::{Change, GenericExpr, Literal};
40use egglog_ast::span::Span;
41use egglog_ast::util::ListDisplay;
42use egglog_bridge::{ColumnTy, QueryEntry};
43use egglog_core_relations as core_relations;
44use egglog_numeric_id as numeric_id;
45use egglog_reports::{ReportLevel, RunReport};
46pub use exec_state::{
47    Context, Core, Enode, FullState, FunctionEntry, PureState, Read, ReadState, Write, WriteState,
48};
49use extract::{DefaultCost, Extractor, TreeAdditiveCostModel};
50use indexmap::map::Entry;
51use log::{Level, log_enabled};
52use numeric_id::DenseIdMap;
53use prelude::*;
54pub use proofs::proof_encoding_helpers::{file_supports_proofs, program_supports_proofs};
55
56/// Read-only proof reconstruction API.
57pub mod proof {
58    pub use crate::proofs::proof_format::{Justification, Proof, ProofId, ProofStore, Proposition};
59}
60use scheduler::{SchedulerId, SchedulerRecord};
61pub use serialize::{SerializeConfig, SerializeOutput, SerializedNode};
62use sort::*;
63use std::any::{Any, TypeId};
64use std::fmt::{Debug, Display, Formatter};
65use std::fs::File;
66use std::hash::Hash;
67use std::io::{Read as _, Write as _};
68use std::iter::once;
69use std::ops::Deref;
70use std::path::PathBuf;
71use std::sync::Arc;
72pub use termdag::{OrdTerm, Term, TermDag, TermId};
73use thiserror::Error;
74pub use typechecking::PrimitiveValidator;
75pub use typechecking::TypeError;
76pub use typechecking::TypeInfo;
77use util::*;
78
79use crate::ast::desugar::desugar_command;
80use crate::ast::*;
81use crate::core::{GenericActionsExt, ResolvedRuleExt};
82use crate::proofs::proof_encoding::{EncodingState, ProofInstrumentor};
83use crate::proofs::proof_encoding_helpers::{
84    ProofEncodingUnsupportedReason, command_supports_proof_encoding,
85};
86use crate::proofs::proof_extraction::ProveExistsError;
87use crate::proofs::proof_format::{ProofId, ProofStore};
88use crate::proofs::proof_normal_form::proof_form;
89
90pub const GLOBAL_NAME_PREFIX: &str = "$";
91
92pub type ArcSort = Arc<dyn Sort>;
93
94/// Methods shared by every kind-specific primitive trait.
95///
96/// `name` and `get_type_constraints` aren't capability-dependent, so
97/// the four kind-specific traits ([`PurePrim`], [`WritePrim`],
98/// [`ReadPrim`], [`FullPrim`]) share this supertrait.
99pub trait Primitive: Send + Sync + 'static {
100    /// Returns the name of this primitive.
101    fn name(&self) -> &str;
102
103    /// Constructs a type constraint for this primitive.
104    fn get_type_constraints(&self, span: &Span) -> Box<dyn TypeConstraint>;
105}
106
107/// A primitive whose body sees a [`PureState`]. Register via
108/// [`EGraph::add_pure_primitive`].
109pub trait PurePrim: Primitive {
110    fn apply<'a, 'db>(&self, state: PureState<'a, 'db>, args: &[Value]) -> Option<Value>;
111}
112
113/// A primitive whose body sees a [`WriteState`]. Register via
114/// [`EGraph::add_write_primitive`].
115pub trait WritePrim: Primitive {
116    fn apply<'a, 'db>(&self, state: WriteState<'a, 'db>, args: &[Value]) -> Option<Value>;
117}
118
119/// A primitive whose body sees a [`ReadState`]. Register via
120/// [`EGraph::add_read_primitive`].
121pub trait ReadPrim: Primitive {
122    fn apply<'a, 'db>(&self, state: ReadState<'a, 'db>, args: &[Value]) -> Option<Value>;
123}
124
125/// A primitive whose body sees a [`FullState`]. Register via
126/// [`EGraph::add_full_primitive`].
127pub trait FullPrim: Primitive {
128    fn apply<'a, 'db>(&self, state: FullState<'a, 'db>, args: &[Value]) -> Option<Value>;
129}
130
131/// A user-defined command output trait.
132pub trait UserDefinedCommandOutput: Debug + std::fmt::Display + Send + Sync {}
133impl<T> UserDefinedCommandOutput for T where T: Debug + std::fmt::Display + Send + Sync {}
134
135/// Output from a command.
136#[derive(Clone, Debug)]
137#[allow(clippy::large_enum_variant)]
138pub enum CommandOutput {
139    /// The size of a function
140    PrintFunctionSize(usize),
141    /// The name of all functions and their sizes
142    PrintAllFunctionsSize(Vec<(String, usize)>),
143    /// The best term found after extracting
144    ExtractBest(TermDag, DefaultCost, TermId),
145    /// The variants of a function found after extracting. Like normal extraction, but has to choose one extraction per e-node in the e-class.
146    ExtractVariants(TermDag, Vec<TermId>),
147    /// A high-level proof witnessing constructor existence
148    ProveExists {
149        proof_store: ProofStore,
150        proof_id: ProofId,
151    },
152    /// The report from all runs
153    OverallStatistics(RunReport),
154    /// A printed function and all its values
155    PrintFunction(Function, TermDag, Vec<(TermId, TermId)>, PrintFunctionMode),
156    /// The report from a single run
157    RunSchedule(RunReport),
158    /// A user defined output
159    UserDefined(Arc<dyn UserDefinedCommandOutput>),
160}
161
162impl CommandOutput {
163    /// Render command outputs to a string that is identical whether the program
164    /// ran normally or under proof encoding (`--proofs`). Drops outputs that
165    /// legitimately differ or are non-deterministic (timing, `PrintFunction`
166    /// per #793, extraction variants) and reduces `ExtractBest` to its cost.
167    pub fn snapshot_stable_under_proof_encoding(outputs: &[CommandOutput]) -> String {
168        outputs
169            .iter()
170            .filter_map(|output| match output {
171                CommandOutput::OverallStatistics(_) => None,
172                CommandOutput::PrintFunction(..) => None,
173                CommandOutput::ExtractBest(_, cost, _) => {
174                    Some(format!("(extraction-costs {cost})\n"))
175                }
176                CommandOutput::ExtractVariants(..) => None,
177                other => Some(other.to_string()),
178            })
179            .collect::<Vec<_>>()
180            .join("")
181    }
182}
183
184impl std::fmt::Display for CommandOutput {
185    /// Format the command output for display, ending with a newline.
186    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
187        match self {
188            CommandOutput::PrintFunctionSize(size) => writeln!(f, "{size}"),
189            CommandOutput::PrintAllFunctionsSize(names_and_sizes) => {
190                write!(f, "(")?;
191                for (i, (name, size)) in names_and_sizes.iter().enumerate() {
192                    // indent except for the first line
193                    if i > 0 {
194                        write!(f, " ")?;
195                    }
196                    // write the pair of funciton symbol and size
197                    write!(f, "({name} {size})")?;
198                    // add a newline except at the end
199                    if i < names_and_sizes.len() - 1 {
200                        writeln!(f)?;
201                    }
202                }
203                writeln!(f, ")")
204            }
205            CommandOutput::ExtractBest(termdag, _cost, term) => {
206                writeln!(f, "{}", termdag.to_string(*term))
207            }
208            CommandOutput::ExtractVariants(termdag, terms) => {
209                writeln!(f, "(")?;
210                for expr in terms {
211                    writeln!(f, "   {}", termdag.to_string(*expr))?;
212                }
213                writeln!(f, ")")
214            }
215            CommandOutput::ProveExists {
216                proof_store,
217                proof_id,
218            } => writeln!(f, "{}", proof_store.proof_to_string(*proof_id)),
219            CommandOutput::OverallStatistics(run_report) => {
220                write!(f, "Overall statistics:\n{run_report}")
221            }
222            CommandOutput::PrintFunction(function, termdag, terms_and_outputs, mode) => {
223                let out_is_unit = function.schema.output.name() == UnitSort.name();
224                if *mode == PrintFunctionMode::CSV {
225                    let mut wtr = Writer::from_writer(vec![]);
226                    for (term_id, output) in terms_and_outputs {
227                        let term = termdag.get(*term_id);
228                        match term {
229                            Term::App(name, children) => {
230                                let mut values = vec![name.clone()];
231                                for child_id in children {
232                                    values.push(termdag.to_string(*child_id));
233                                }
234
235                                if !out_is_unit {
236                                    values.push(termdag.to_string(*output));
237                                }
238                                wtr.write_record(&values).map_err(|_| std::fmt::Error)?;
239                            }
240                            _ => panic!("Expect function_to_dag to return a list of apps."),
241                        }
242                    }
243                    let csv_bytes = wtr.into_inner().map_err(|_| std::fmt::Error)?;
244                    f.write_str(&String::from_utf8(csv_bytes).map_err(|_| std::fmt::Error)?)
245                } else {
246                    writeln!(f, "(")?;
247                    for (term, output) in terms_and_outputs.iter() {
248                        write!(f, "   {}", termdag.to_string(*term))?;
249                        if !out_is_unit {
250                            write!(f, " -> {}", termdag.to_string(*output))?;
251                        }
252                        writeln!(f)?;
253                    }
254                    writeln!(f, ")")
255                }
256            }
257            CommandOutput::RunSchedule(_report) => Ok(()),
258            CommandOutput::UserDefined(output) => {
259                write!(f, "{}", *output)
260            }
261        }
262    }
263}
264
265/// The main interface for an e-graph in egglog.
266///
267/// An [`EGraph`] maintains a collection of equivalence classes of terms and provides
268/// operations for adding facts, running rules, and extracting optimal terms.
269///
270/// # Examples
271///
272/// ```
273/// use egglog::*;
274///
275/// let mut egraph = EGraph::default();
276/// egraph.parse_and_run_program(None, "(datatype Math (Num i64) (Add Math Math))").unwrap();
277/// ```
278trait ExtensionStateValue: Any + dyn_clone::DynClone + Send + Sync {}
279
280impl<T> ExtensionStateValue for T where T: Any + Clone + Send + Sync {}
281
282dyn_clone::clone_trait_object!(ExtensionStateValue);
283
284#[derive(Clone)]
285pub struct EGraph {
286    backend: egglog_bridge::EGraph,
287    pub parser: Parser,
288    names: check_shadowing::Names,
289    /// pushed_egraph forms a linked list of pushed egraphs.
290    /// Pop reverts the egraph to the last pushed egraph.
291    pushed_egraph: Option<Box<Self>>,
292    functions: IndexMap<String, Function>,
293    rulesets: IndexMap<String, Ruleset>,
294    pub fact_directory: Option<PathBuf>,
295    pub seminaive: bool,
296    pub no_decomp: bool,
297    type_info: TypeInfo,
298    /// The run report unioned over all runs so far.
299    overall_run_report: RunReport,
300    schedulers: DenseIdMap<SchedulerId, SchedulerRecord>,
301    commands: IndexMap<String, Arc<dyn UserDefinedCommand>>,
302    extension_state: HashMap<TypeId, Box<dyn ExtensionStateValue>>,
303    strict_mode: bool,
304    warned_about_global_prefix: bool,
305    /// Registry for command-level macros
306    command_macros: CommandMacroRegistry,
307    proof_state: EncodingState,
308    /// In proof mode, this is the program before proof instrumentation and the version we use for proof checking.
309    proof_check_program: Vec<ResolvedNCommand>,
310}
311
312/// A user-defined command allows users to inject custom command that can be called
313/// in an egglog program.
314///
315/// Compared to an external function, a user-defined command is more powerful because
316/// it has an exclusive access to the e-graph.
317pub trait UserDefinedCommand: Send + Sync {
318    /// Run the command with the given arguments.
319    fn update(&self, egraph: &mut EGraph, args: &[Expr]) -> Result<Vec<CommandOutput>, Error>;
320}
321
322/// A function in the e-graph.
323///
324/// This contains the schema information of the function and
325/// the backend id of the function in the e-graph.
326#[derive(Clone)]
327pub struct Function {
328    decl: ResolvedFunctionDecl,
329    schema: ResolvedSchema,
330    can_subsume: bool,
331    backend_id: egglog_bridge::FunctionId,
332}
333
334impl Function {
335    /// Get the name of the function.
336    pub fn name(&self) -> &str {
337        &self.decl.name
338    }
339
340    /// Get the schema of the function.
341    pub fn schema(&self) -> &ResolvedSchema {
342        &self.schema
343    }
344
345    /// Whether this function supports subsumption.
346    pub fn can_subsume(&self) -> bool {
347        self.can_subsume
348    }
349
350    /// Whether this is a let binding
351    pub fn is_let_binding(&self) -> bool {
352        self.decl.internal_let
353    }
354
355    /// Whether this function is internally hidden (e.g., compiler-generated
356    /// helper tables that should not appear in user-facing listings).
357    pub fn is_hidden(&self) -> bool {
358        self.decl.internal_hidden
359    }
360
361    /// The term-constructor name associated with this function table, if
362    /// any. Set on view tables created by the term/proof encoding to refer
363    /// back to the user-visible constructor name.
364    pub fn term_constructor(&self) -> Option<&str> {
365        self.decl.term_constructor.as_deref()
366    }
367}
368
369#[derive(Clone, Debug)]
370pub struct ResolvedSchema {
371    pub input: Vec<ArcSort>,
372    pub output: ArcSort,
373}
374
375impl ResolvedSchema {
376    /// Get the type at position `index`, counting the `output` sort as at position `input.len()`.
377    pub fn get_by_pos(&self, index: usize) -> Option<&ArcSort> {
378        if self.input.len() == index {
379            Some(&self.output)
380        } else {
381            self.input.get(index)
382        }
383    }
384}
385
386impl Debug for Function {
387    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
388        f.debug_struct("Function")
389            .field("decl", &self.decl)
390            .field("schema", &self.schema)
391            .finish()
392    }
393}
394
395impl Default for EGraph {
396    fn default() -> Self {
397        let mut parser = Parser::default();
398        let proof_state = EncodingState::new(&mut parser.symbol_gen);
399        let mut eg = Self {
400            backend: Default::default(),
401            parser,
402            names: Default::default(),
403            pushed_egraph: Default::default(),
404            functions: Default::default(),
405            rulesets: Default::default(),
406            fact_directory: None,
407            seminaive: true,
408            no_decomp: false,
409            overall_run_report: Default::default(),
410            type_info: Default::default(),
411            schedulers: Default::default(),
412            commands: Default::default(),
413            extension_state: Default::default(),
414            strict_mode: false,
415            warned_about_global_prefix: false,
416            command_macros: Default::default(),
417            proof_state,
418            proof_check_program: vec![],
419        };
420        add_base_sort(&mut eg, UnitSort, span!()).unwrap();
421        add_base_sort(&mut eg, StringSort, span!()).unwrap();
422        add_base_sort(&mut eg, BoolSort, span!()).unwrap();
423        add_base_sort(&mut eg, I64Sort, span!()).unwrap();
424        add_base_sort(&mut eg, F64Sort, span!()).unwrap();
425        add_base_sort(&mut eg, BigIntSort, span!()).unwrap();
426        add_base_sort(&mut eg, BigRatSort, span!()).unwrap();
427        eg.type_info.add_presort::<MapSort>(span!()).unwrap();
428        eg.type_info.add_presort::<SetSort>(span!()).unwrap();
429        eg.type_info.add_presort::<VecSort>(span!()).unwrap();
430        eg.type_info.add_presort::<FunctionSort>(span!()).unwrap();
431        eg.type_info.add_presort::<MultiSetSort>(span!()).unwrap();
432        eg.type_info.add_presort::<PairSort>(span!()).unwrap();
433
434        // Add != with a validator that computes inequality result
435        let neq_validator = |termdag: &mut TermDag, args: &[TermId]| -> Option<TermId> {
436            if args.len() == 2 && args[0] != args[1] {
437                // Return unit literal for successful inequality
438                Some(termdag.lit(Literal::Unit))
439            } else {
440                None
441            }
442        };
443        add_primitive_with_validator!(
444            &mut eg,
445            "!=" = |a: #, b: #| -?> () {
446                (a != b).then_some(())
447            },
448            neq_validator
449        );
450
451        add_primitive_with_validator!(
452            &mut eg,
453            "bool-!=" = |a: #, b: #| -> bool {
454                (a != b)
455            },
456            |termdag: &mut TermDag, args: &[TermId]| -> Option<TermId> {
457                if args.len() == 2 {
458                    Some(termdag.lit(Literal::Bool(args[0] != args[1])))
459                } else {
460                    None
461                }
462            }
463        );
464
465        add_primitive!(&mut eg, "value-eq" = |a: #, b: #| -?> () {
466            (a == b).then_some(())
467        });
468        add_primitive!(&mut eg, "ordering-min" = |a: #, b: #| -> # {
469            if a < b { a } else { b }
470        });
471        add_primitive!(&mut eg, "ordering-max" = |a: #, b: #| -> # {
472            if a > b { a } else { b }
473        });
474
475        eg.rulesets
476            .insert("".into(), Ruleset::Rules(Default::default()));
477
478        eg
479    }
480}
481
482struct ResolvedNCommands {
483    desugared: Vec<ResolvedNCommand>,
484    /// In proof mode, populated with the desugared program before instrumented with proofs
485    desugared_before_proofs: Vec<ResolvedNCommand>,
486}
487
488struct ResolvedNCommandsWithOutput {
489    outputs: Vec<CommandOutput>,
490    resolved: Vec<ResolvedNCommand>,
491    /// In proof mode, populated with the desugared program before instrumented with proofs
492    resolved_before_proofs: Vec<ResolvedNCommand>,
493}
494
495#[derive(Debug, Error)]
496#[error("Not found: {0}")]
497pub struct NotFoundError(String);
498
499impl EGraph {
500    /// Create a new e-graph configured with `num_threads`.
501    ///
502    /// Passing `1` keeps execution serial. Passing `0` uses available
503    /// parallelism.
504    pub fn new(num_threads: usize) -> Self {
505        EGraph::default().with_num_threads(num_threads)
506    }
507
508    /// Create a new e-graph with the term-encoding pipeline enabled.
509    ///
510    /// In term-encoding mode the e-graph eagerly instruments every constructor
511    /// and function with auxiliary term tables, view tables, and per-sort
512    /// union-finds so that canonical representatives and their justifications are
513    /// materialized explicitly.  This makes it possible to record and emit
514    /// equality proofs while preserving the observable behaviour of supported
515    /// commands.
516    pub fn new_with_term_encoding() -> Self {
517        let mut egraph = EGraph::default();
518        egraph.proof_state.original_typechecking = Some(Box::new(egraph.clone()));
519        egraph
520    }
521
522    /// Create a new e-graph with proof generation enabled.
523    pub fn new_with_proofs() -> Self {
524        let mut egraph = EGraph::new_with_term_encoding();
525        egraph.proof_state.proofs_enabled = true;
526        egraph
527    }
528
529    /// Enable the term-encoding pipeline on an existing `EGraph`.
530    ///
531    /// This method is to support the current CLI implementation with egglog-experimental (https://github.com/egraphs-good/egglog/issues/768)
532    #[cfg(feature = "bin")]
533    pub(crate) fn with_term_encoding_enabled(mut self) -> Self {
534        self.proof_state.original_typechecking = Some(Box::new(self.clone()));
535        self
536    }
537
538    /// Enable proof generation on this e-graph.
539    /// TODO proofs should be turned on during creation of the e-graph, not afterwards.
540    /// This method is to support the current CLI implementation with egglog-experimental (https://github.com/egraphs-good/egglog/issues/768)
541    #[cfg(feature = "bin")]
542    pub(crate) fn with_proofs_enabled(mut self) -> Self {
543        self = self.with_term_encoding_enabled();
544        self.proof_state.proofs_enabled = true;
545        self
546    }
547
548    /// Enable testing of getting proofs for all `check` commands.
549    pub fn with_proof_testing(mut self) -> Self {
550        self.proof_state.proof_testing = true;
551        self
552    }
553
554    /// Return a copy of this e-graph configured with `num_threads`.
555    pub fn with_num_threads(mut self, num_threads: usize) -> Self {
556        self.set_num_threads(num_threads);
557        self
558    }
559
560    /// Set the number of threads used by this e-graph.
561    ///
562    /// Passing `1` keeps execution serial. Passing `0` uses available
563    /// parallelism.
564    pub fn set_num_threads(&mut self, num_threads: usize) {
565        self.backend.set_num_threads(num_threads);
566        if let Some(original) = &mut self.proof_state.original_typechecking {
567            original.set_num_threads(num_threads);
568        }
569    }
570
571    /// Return the number of threads configured for this e-graph.
572    pub fn num_threads(&self) -> usize {
573        self.backend.num_threads()
574    }
575
576    /// Return extension-owned state stored on this e-graph.
577    ///
578    /// Extension state is keyed by Rust type and follows the same lifecycle as
579    /// the rest of the e-graph: cloning an [`EGraph`] clones the state, and
580    /// `push`/`pop` snapshots and restores it.
581    pub fn extension_state<T>(&self) -> Option<&T>
582    where
583        T: Send + Sync + 'static,
584    {
585        let value = self.extension_state.get(&TypeId::of::<T>())?;
586        (value.as_ref() as &dyn Any).downcast_ref()
587    }
588
589    /// Return mutable extension-owned state, inserting `T::default()` when absent.
590    pub fn extension_state_or_default<T>(&mut self) -> &mut T
591    where
592        T: Default + Clone + Send + Sync + 'static,
593    {
594        let value = self
595            .extension_state
596            .entry(TypeId::of::<T>())
597            .or_insert_with(|| Box::new(T::default()));
598        (value.as_mut() as &mut dyn Any)
599            .downcast_mut()
600            .expect("extension state entry must have the requested type")
601    }
602
603    /// Add a user-defined command to the e-graph
604    /// Get the type information for this e-graph
605    pub fn type_info(&mut self) -> &mut TypeInfo {
606        &mut self.type_info
607    }
608
609    /// Get read-only access to the command macro registry
610    pub fn command_macros(&self) -> &CommandMacroRegistry {
611        &self.command_macros
612    }
613
614    /// Get mutable access to the command macro registry
615    pub fn command_macros_mut(&mut self) -> &mut CommandMacroRegistry {
616        &mut self.command_macros
617    }
618
619    pub fn add_command(
620        &mut self,
621        name: String,
622        command: Arc<dyn UserDefinedCommand>,
623    ) -> Result<(), Error> {
624        if self.commands.contains_key(&name)
625            || self.functions.contains_key(&name)
626            || self.type_info.get_prims(&name).is_some()
627        {
628            return Err(Error::CommandAlreadyExists(name, span!()));
629        }
630        self.commands.insert(name.clone(), command);
631        self.parser.add_user_defined(name)?;
632        Ok(())
633    }
634
635    /// Configure whether globals missing the required `$` prefix are treated as errors.
636    pub fn set_strict_mode(&mut self, strict_mode: bool) {
637        self.strict_mode = strict_mode;
638    }
639
640    /// Returns `true` when missing `$` prefixes on globals are treated as errors.
641    pub fn strict_mode(&self) -> bool {
642        self.strict_mode
643    }
644
645    /// Configure whether the internal reserved symbol (@) is allowed in user-defined names.
646    /// WARNING: do not use, this is for testing running egglog after desugaring.
647    /// Public so files.rs can use it, hidden from documentation because it is not intended for general use.
648    #[doc(hidden)]
649    pub fn ensure_no_reserved_symbols(&mut self, should_ensure: bool) {
650        self.parser.ensure_no_reserved_symbols = should_ensure;
651    }
652
653    fn ensure_global_name_prefix(&mut self, span: &Span, name: &str) -> Result<(), TypeError> {
654        if name.starts_with(GLOBAL_NAME_PREFIX) {
655            return Ok(());
656        }
657        if self.strict_mode {
658            Err(TypeError::GlobalMissingPrefix {
659                name: name.to_owned(),
660                span: span.clone(),
661            })
662        } else {
663            self.warn_missing_global_prefix(span, name)?;
664            Ok(())
665        }
666    }
667
668    fn warn_missing_global_prefix(
669        &mut self,
670        span: &Span,
671        canonical_name: &str,
672    ) -> Result<(), TypeError> {
673        if self.strict_mode {
674            return Err(TypeError::GlobalMissingPrefix {
675                name: format!("{GLOBAL_NAME_PREFIX}{canonical_name}"),
676                span: span.clone(),
677            });
678        }
679        if self.warned_about_global_prefix {
680            return Ok(());
681        }
682        self.warned_about_global_prefix = true;
683        log::warn!(
684            "{span}\nGlobal `{canonical_name}` should start with `{GLOBAL_NAME_PREFIX}`. Enable `--strict-mode` to turn this warning into an error. Suppressing additional warnings of this type."
685        );
686        Ok(())
687    }
688
689    fn warn_prefixed_non_globals(
690        &mut self,
691        span: &Span,
692        canonical_name: &str,
693    ) -> Result<(), TypeError> {
694        if self.strict_mode {
695            return Err(TypeError::NonGlobalPrefixed {
696                name: canonical_name.to_string(),
697                span: span.clone(),
698            });
699        }
700        if self.warned_about_global_prefix {
701            return Ok(());
702        }
703        self.warned_about_global_prefix = true;
704        log::warn!(
705            "{span}\nNon-global `{canonical_name}` should not start with `{GLOBAL_NAME_PREFIX}`. Enable `--strict-mode` to turn this warning into an error. Suppressing additional warnings of this type."
706        );
707        Ok(())
708    }
709
710    /// Push a snapshot of the e-graph into the stack.
711    ///
712    /// See [`EGraph::pop`].
713    pub fn push(&mut self) {
714        let prev_prev: Option<Box<Self>> = self.pushed_egraph.take();
715        let mut prev = self.clone();
716        prev.pushed_egraph = prev_prev;
717        self.pushed_egraph = Some(Box::new(prev));
718    }
719
720    /// Pop the current egraph off the stack, replacing
721    /// it with the previously pushed egraph.
722    /// It preserves the run report and messages from the popped
723    /// egraph.
724    pub fn pop(&mut self) -> Result<(), Error> {
725        match self.pushed_egraph.take() {
726            Some(mut e) => {
727                // Preserve the overall report from the popped egraph
728                std::mem::swap(&mut self.overall_run_report, &mut e.overall_run_report);
729                // Preserve the symbol generator so that fresh symbols
730                // generated after pop don't collide with ones generated before pop.
731                std::mem::swap(&mut self.parser.symbol_gen, &mut e.parser.symbol_gen);
732                *self = *e;
733                Ok(())
734            }
735            None => Err(Error::Pop(span!())),
736        }
737    }
738
739    fn translate_expr_to_mergefn(
740        &self,
741        expr: &ResolvedExpr,
742    ) -> Result<egglog_bridge::MergeFn, Error> {
743        match expr {
744            GenericExpr::Lit(_, literal) => {
745                let val = literal_to_value(&self.backend, literal);
746                Ok(egglog_bridge::MergeFn::Const(val))
747            }
748            GenericExpr::Var(span, resolved_var) => match resolved_var.name.as_str() {
749                "old" => Ok(egglog_bridge::MergeFn::Old),
750                "new" => Ok(egglog_bridge::MergeFn::New),
751                // NB: type-checking should already catch unbound variables here.
752                _ => Err(TypeError::Unbound(resolved_var.name.clone(), span.clone()).into()),
753            },
754            GenericExpr::Call(_, ResolvedCall::Func(f), args) => {
755                let translated_args = args
756                    .iter()
757                    .map(|arg| self.translate_expr_to_mergefn(arg))
758                    .collect::<Result<Vec<_>, _>>()?;
759                Ok(egglog_bridge::MergeFn::Function(
760                    self.functions[&f.name].backend_id,
761                    translated_args,
762                ))
763            }
764            GenericExpr::Call(_, ResolvedCall::Primitive(p), args) => {
765                let mut translated_args = args
766                    .iter()
767                    .map(|arg| self.translate_expr_to_mergefn(arg))
768                    .collect::<Result<Vec<_>, _>>()?;
769                if p.name() == "unstable-fn" {
770                    let Some(GenericExpr::Lit(_, Literal::String(name))) = args.first() else {
771                        return Err(Error::BackendError(
772                            "expected string literal after `unstable-fn`".into(),
773                        ));
774                    };
775                    let resolved = resolve_function_container_target_with_context(
776                        &self.backend,
777                        &self.functions,
778                        &self.type_info,
779                        name,
780                        p,
781                        self.backend
782                            .action_registry()
783                            .read()
784                            .unwrap()
785                            .default_panic_id(),
786                    )?;
787                    translated_args[0] =
788                        egglog_bridge::MergeFn::Const(self.backend.base_values().get(resolved));
789                }
790                Ok(egglog_bridge::MergeFn::Primitive(
791                    p.external_id(crate::Context::Write),
792                    translated_args,
793                ))
794            }
795        }
796    }
797
798    fn declare_function(&mut self, decl: &ResolvedFunctionDecl) -> Result<(), Error> {
799        let get_sort = |name: &String| match self.type_info.get_sort_by_name(name) {
800            Some(sort) => Ok(sort.clone()),
801            None => Err(Error::TypeError(TypeError::UndefinedSort(
802                name.to_owned(),
803                decl.span.clone(),
804            ))),
805        };
806
807        let input = decl
808            .schema
809            .input
810            .iter()
811            .map(get_sort)
812            .collect::<Result<Vec<_>, _>>()?;
813        let output = get_sort(&decl.schema.output)?;
814
815        let can_subsume = match decl.subtype {
816            FunctionSubtype::Constructor => true,
817            // View tables (functions with term_constructor) need subsumption support
818            FunctionSubtype::Custom => decl.term_constructor.is_some(),
819        };
820
821        use egglog_bridge::{DefaultVal, MergeFn};
822        let backend_id = self.backend.add_table(egglog_bridge::FunctionConfig {
823            schema: input
824                .iter()
825                .chain([&output])
826                .map(|sort| sort.column_ty(&self.backend))
827                .collect(),
828            default: match decl.subtype {
829                FunctionSubtype::Constructor => DefaultVal::FreshId,
830                FunctionSubtype::Custom => DefaultVal::Fail,
831            },
832            merge: match decl.subtype {
833                FunctionSubtype::Constructor => MergeFn::UnionId,
834                FunctionSubtype::Custom => match &decl.merge {
835                    None => MergeFn::AssertEq,
836                    Some(expr) => self.translate_expr_to_mergefn(expr)?,
837                },
838            },
839            name: decl.name.to_string(),
840            can_subsume,
841        });
842
843        let function = Function {
844            decl: decl.clone(),
845            schema: ResolvedSchema { input, output },
846            can_subsume,
847            backend_id,
848        };
849
850        let old = self.functions.insert(decl.name.clone(), function);
851        if old.is_some() {
852            panic!(
853                "Typechecking should have caught function already bound: {}",
854                decl.name
855            );
856        }
857
858        Ok(())
859    }
860
861    /// Extract rows of a table using the default cost model with name sym
862    /// The `include_output` parameter controls whether the output column is always extracted
863    /// For functions, the output column is usually useful
864    /// Print up to `n` the tuples in a given function.
865    /// Print all tuples if `n` is not provided.
866    pub fn print_function(
867        &mut self,
868        sym: &str,
869        n: Option<usize>,
870        file: Option<File>,
871        mode: PrintFunctionMode,
872    ) -> Result<Option<CommandOutput>, Error> {
873        let n = match n {
874            Some(n) => {
875                log::info!("Printing up to {n} tuples of function {sym} as {mode}");
876                n
877            }
878            None => {
879                log::info!("Printing all tuples of function {sym} as {mode}");
880                usize::MAX
881            }
882        };
883
884        let (terms, outputs, termdag) = self.function_to_dag(sym, n, true)?;
885        let f = self
886            .functions
887            .get(sym)
888            // function_to_dag should have checked this
889            .unwrap();
890        let terms_and_outputs: Vec<_> = terms.into_iter().zip(outputs.unwrap()).collect();
891        let output = CommandOutput::PrintFunction(f.clone(), termdag, terms_and_outputs, mode);
892        match file {
893            Some(mut file) => {
894                log::info!("Writing output to file");
895                file.write_all(output.to_string().as_bytes())
896                    .expect("Error writing to file");
897                Ok(None)
898            }
899            None => Ok(Some(output)),
900        }
901    }
902
903    /// Provide a program for use in proof checking.
904    /// This enables testing of a desugared egglog proof program outside of proof mode.
905    /// When proof_testing is true, turns all the `check` commands into `prove` commands.
906    /// Not intended for general use but needed in files.rs, so public but hidden.
907    #[doc(hidden)]
908    pub fn set_proof_checking_program(
909        &mut self,
910        prog: Vec<Command>,
911        proof_testing: bool,
912    ) -> Result<(), Error> {
913        // make a new e-graph, desugar the program in proof mode
914        let mut proof_check_eg = EGraph::new_with_proofs();
915        if proof_testing {
916            proof_check_eg = proof_check_eg.with_proof_testing();
917        }
918        let resolved = proof_check_eg.process_program_internal(prog, false)?;
919
920        self.proof_check_program = resolved.resolved_before_proofs;
921        Ok(())
922    }
923
924    /// Print the size of a function. If no function name is provided,
925    /// print the size of all non-hidden functions as an s-expression list of
926    /// `(name size)` pairs, e.g. `((name size) ...)`.
927    pub fn print_size(&self, sym: Option<&str>) -> Result<CommandOutput, Error> {
928        if let Some(sym) = sym {
929            // In proof mode, we have view tables instead of term tables.
930            // So we do a linear scan to find the view table first, falling back on the normal table otherwise.
931            // (We don't check the proof mode flag so that this still works after desugaring)
932            let f = self
933                .functions
934                .values()
935                .find(|f| f.decl.term_constructor.as_deref() == Some(sym))
936                .or_else(|| self.functions.get(sym))
937                .ok_or(TypeError::UnboundFunction(sym.to_owned(), span!()))?;
938            // Skip hidden and let_binding functions
939            if f.decl.internal_hidden || f.decl.internal_let {
940                return Err(TypeError::UnboundFunction(sym.to_owned(), span!()).into());
941            }
942            let size = self.backend.table_size(f.backend_id);
943            log::info!("Function {sym} has size {size}");
944            Ok(CommandOutput::PrintFunctionSize(size))
945        } else {
946            // Print size of all non-hidden, non-let_binding functions
947            // For view tables, use the term_constructor name instead
948            let mut lens = self
949                .functions
950                .iter()
951                .filter(|(_, f)| !f.decl.internal_hidden && !f.decl.internal_let)
952                .map(|(sym, f)| {
953                    let name = f
954                        .decl
955                        .term_constructor
956                        .clone()
957                        .unwrap_or_else(|| sym.clone());
958                    (name, self.backend.table_size(f.backend_id))
959                })
960                .collect::<Vec<_>>();
961
962            // Function name's alphabetical order
963            lens.sort_by_key(|(name, _)| name.clone());
964            if log_enabled!(Level::Info) {
965                for (sym, len) in &lens {
966                    log::info!("Function {sym} has size {len}");
967                }
968            }
969            Ok(CommandOutput::PrintAllFunctionsSize(lens))
970        }
971    }
972
973    // returns whether the egraph was updated
974    fn run_schedule(&mut self, sched: &ResolvedSchedule) -> Result<RunReport, Error> {
975        match sched {
976            ResolvedSchedule::Run(span, config) => self.run_rules(span, config),
977            ResolvedSchedule::Repeat(_span, limit, sched) => {
978                let mut report = RunReport::default();
979                for _i in 0..*limit {
980                    let rec = self.run_schedule(sched)?;
981                    let can_stop = rec.can_stop;
982                    report.union(rec);
983                    if can_stop {
984                        break;
985                    }
986                }
987                Ok(report)
988            }
989            ResolvedSchedule::Saturate(_span, sched) => {
990                let mut report = RunReport::default();
991                let mut i = 0usize;
992                loop {
993                    i += 1;
994                    log::debug!(
995                        "Saturate iteration {i} start: {}",
996                        Self::schedule_for_log(sched)
997                    );
998                    let rec = self.run_schedule(sched)?;
999                    let updated = rec.updated;
1000                    log::debug!(
1001                        "Saturate iteration {i} end: {}",
1002                        Self::run_report_debug_summary(&rec)
1003                    );
1004                    report.union(rec);
1005                    if !updated {
1006                        log::debug!("Saturate reached fixpoint after {i} iteration(s)");
1007                        break;
1008                    }
1009                }
1010                Ok(report)
1011            }
1012            ResolvedSchedule::Sequence(_span, scheds) => {
1013                let mut report = RunReport::default();
1014                for sched in scheds {
1015                    report.union(self.run_schedule(sched)?);
1016                }
1017                Ok(report)
1018            }
1019        }
1020    }
1021
1022    fn run_rules(&mut self, span: &Span, config: &ResolvedRunConfig) -> Result<RunReport, Error> {
1023        log::debug!("Running ruleset: {}", config.ruleset);
1024        let mut report: RunReport = Default::default();
1025
1026        let GenericRunConfig { ruleset, until } = config;
1027
1028        if !self.rulesets.contains_key(ruleset) {
1029            return Err(Error::NoSuchRuleset(ruleset.clone(), span.clone()));
1030        }
1031
1032        if let Some(facts) = until
1033            && self.check_facts(span, facts).is_ok()
1034        {
1035            log::info!(
1036                "Breaking early because of facts:\n {}!",
1037                ListDisplay(facts, "\n")
1038            );
1039            return Ok(report);
1040        }
1041
1042        let subreport = self.step_rules(ruleset)?;
1043        report.union(subreport);
1044
1045        if log_enabled!(Level::Debug) {
1046            log::debug!(
1047                "Finished ruleset {ruleset}: database size {}, {}",
1048                self.num_tuples(),
1049                Self::run_report_debug_summary(&report)
1050            );
1051        }
1052
1053        Ok(report)
1054    }
1055
1056    fn run_report_debug_summary(report: &RunReport) -> String {
1057        let mut rules = report
1058            .num_matches_per_rule
1059            .iter()
1060            .filter(|(_, matches)| **matches > 0)
1061            .collect::<Vec<_>>();
1062        rules.sort_by(|(_, left), (_, right)| right.cmp(left));
1063
1064        let top_rules = rules
1065            .into_iter()
1066            .take(5)
1067            .map(|(rule, matches)| {
1068                format!("{}={matches}", Self::truncate_for_log(rule.as_ref(), 80))
1069            })
1070            .collect::<Vec<_>>()
1071            .join(", ");
1072
1073        format!(
1074            "updated={}, can_stop={}, iterations={}, top_matches=[{}]",
1075            report.updated,
1076            report.can_stop,
1077            report.iterations.len(),
1078            top_rules
1079        )
1080    }
1081
1082    fn schedule_for_log(sched: &ResolvedSchedule) -> String {
1083        Self::truncate_for_log(&sched.to_string(), 160)
1084    }
1085
1086    fn truncate_for_log(s: &str, limit: usize) -> String {
1087        let mut s = s.replace('\n', " ");
1088        if s.len() > limit {
1089            s.truncate(limit);
1090            s.push_str("...");
1091        }
1092        s
1093    }
1094
1095    /// Runs a ruleset for an iteration.
1096    ///
1097    /// This applies every match it finds (under semi-naive).
1098    /// See [`EGraph::step_rules_with_scheduler`] for more fine-grained control.
1099    ///
1100    /// This will return an error if an egglog primitive returns None in an action.
1101    pub fn step_rules(&mut self, ruleset: &str) -> Result<RunReport, Error> {
1102        fn collect_rule_ids(
1103            ruleset: &str,
1104            rulesets: &IndexMap<String, Ruleset>,
1105            ids: &mut Vec<egglog_bridge::RuleId>,
1106        ) {
1107            match &rulesets[ruleset] {
1108                Ruleset::Rules(rules) => {
1109                    for (_, id) in rules.values() {
1110                        ids.push(*id);
1111                    }
1112                }
1113                Ruleset::Combined(sub_rulesets) => {
1114                    for sub_ruleset in sub_rulesets {
1115                        collect_rule_ids(sub_ruleset, rulesets, ids);
1116                    }
1117                }
1118            }
1119        }
1120
1121        let mut rule_ids = Vec::new();
1122        collect_rule_ids(ruleset, &self.rulesets, &mut rule_ids);
1123
1124        let iteration_report = self
1125            .backend
1126            .run_rules(&rule_ids)
1127            .map_err(|e| Error::BackendError(e.to_string()))?;
1128
1129        Ok(RunReport::singleton(ruleset, iteration_report))
1130    }
1131
1132    fn add_rule(&mut self, rule: ast::ResolvedRule) -> Result<String, Error> {
1133        // Disable union_to_set optimization in proof or term encoding mode, since
1134        // it expects only `union` on constructors (not set).
1135        let core_rule = rule.to_canonicalized_core_rule(
1136            &self.type_info,
1137            &mut self.parser.symbol_gen,
1138            self.proof_state.original_typechecking.is_none(),
1139        )?;
1140        let (query, actions) = (&core_rule.body, &core_rule.head);
1141
1142        // The `:naive` rule option opts a single rule out of seminaive
1143        // evaluation. This widens primitive-context selection from
1144        // Pure/Write to Read/Full, so primitives that read or write the
1145        // database can run inside this rule.
1146        let seminaive = self.seminaive && !rule.eval_mode.is_naive();
1147        // The `:no-decomp` rule option (and the global `--no-decomp`
1148        // flag) skips tree-decomposition in query planning, forcing
1149        // the single-bag fast path.
1150        let no_decomp = self.no_decomp || rule.no_decomp;
1151        let requires_read_context = !seminaive
1152            || matches!(
1153                rule.eval_mode,
1154                RuleEvalMode::Naive | RuleEvalMode::UnsafeSeminaive
1155            );
1156
1157        let rule_id = {
1158            let mut rb = self.backend.new_rule(&rule.name, seminaive);
1159            rb.set_no_decomp(no_decomp);
1160            let mut translator =
1161                BackendRule::new(rb, &self.functions, &self.type_info, requires_read_context);
1162            translator.query(query, rule.include_subsumed);
1163            translator.actions(actions)?;
1164            translator.build()
1165        };
1166
1167        if let Some(rules) = self.rulesets.get_mut(&rule.ruleset) {
1168            match rules {
1169                Ruleset::Rules(rules) => {
1170                    match rules.entry(rule.name.clone()) {
1171                        indexmap::map::Entry::Occupied(_) => {
1172                            let name = rule.name;
1173                            panic!("Rule '{name}' was already present")
1174                        }
1175                        indexmap::map::Entry::Vacant(e) => e.insert((core_rule, rule_id)),
1176                    };
1177                    Ok(rule.name)
1178                }
1179                Ruleset::Combined(_) => Err(Error::CombinedRulesetError(rule.ruleset, rule.span)),
1180            }
1181        } else {
1182            Err(Error::NoSuchRuleset(rule.ruleset, rule.span))
1183        }
1184    }
1185
1186    fn eval_actions(&mut self, actions: &ResolvedActions) -> Result<(), Error> {
1187        let mut binding = IndexSet::default();
1188        let mut ctx = CoreActionContext::new(
1189            &self.type_info,
1190            &mut binding,
1191            &mut self.parser.symbol_gen,
1192            self.proof_state.original_typechecking.is_none(),
1193        );
1194        let (actions, _) = actions.to_core_actions(&mut ctx)?;
1195
1196        let mut translator = BackendRule::new(
1197            self.backend.new_rule("eval_actions", false),
1198            &self.functions,
1199            &self.type_info,
1200            true, // global action: Read/Full contexts (may read the DB)
1201        );
1202        translator.actions(&actions)?;
1203        let id = translator.build();
1204        let result = self.backend.run_rules(&[id]);
1205        self.backend.free_rule(id);
1206
1207        match result {
1208            Ok(_) => Ok(()),
1209            Err(e) => Err(Error::BackendError(e.to_string())),
1210        }
1211    }
1212
1213    /// Get the list of all functions in the e-graph.
1214    pub fn get_function_names(&self) -> Vec<String> {
1215        self.functions.keys().cloned().collect()
1216    }
1217
1218    /// Iterate over every `(name, function)` pair registered in the
1219    /// e-graph, in registration order.
1220    pub fn functions_iter(&self) -> impl Iterator<Item = (&String, &Function)> {
1221        self.functions.iter()
1222    }
1223
1224    /// Run a read-only closure against the e-graph. The closure receives
1225    /// a [`ReadState`], so it can read but not write. Because this
1226    /// borrows `&self`, the closure and its callbacks may also call other
1227    /// `&self` methods such as [`EGraph::value_to_base`].
1228    pub fn read<R>(&self, f: impl FnOnce(ReadState<'_, '_>) -> R) -> R {
1229        let registry = self.backend.action_registry().clone();
1230        let guard = registry.read().unwrap();
1231        self.backend
1232            .with_execution_state_tracked(|es| f(ReadState::wrap(es, &guard, Context::Read)))
1233            .0
1234    }
1235
1236    /// Call `f` on each [`FunctionEntry`] of a function table. Top-level
1237    /// form of [`Read::function_entries`]; errors if `name` is a
1238    /// constructor or unregistered.
1239    pub fn function_entries(
1240        &self,
1241        name: &str,
1242        f: impl FnMut(FunctionEntry<'_>),
1243    ) -> Result<(), Error> {
1244        self.read(|rs| rs.function_entries(name, f))
1245    }
1246
1247    /// Like [`EGraph::function_entries`], but stops when `f` returns `false`.
1248    pub fn function_entries_while(
1249        &self,
1250        name: &str,
1251        f: impl FnMut(FunctionEntry<'_>) -> bool,
1252    ) -> Result<(), Error> {
1253        self.read(|rs| rs.function_entries_while(name, f))
1254    }
1255
1256    /// Call `f` on each [`Enode`] of a constructor / relation table.
1257    /// Top-level form of [`Read::constructor_enodes`]; errors if `name`
1258    /// is a function or unregistered.
1259    pub fn constructor_enodes(&self, name: &str, f: impl FnMut(Enode<'_>)) -> Result<(), Error> {
1260        self.read(|rs| rs.constructor_enodes(name, f))
1261    }
1262
1263    /// Like [`EGraph::constructor_enodes`], but stops when `f` returns `false`.
1264    pub fn constructor_enodes_while(
1265        &self,
1266        name: &str,
1267        f: impl FnMut(Enode<'_>) -> bool,
1268    ) -> Result<(), Error> {
1269        self.read(|rs| rs.constructor_enodes_while(name, f))
1270    }
1271
1272    /// Remove every row from the named function in bulk.
1273    ///
1274    /// This is intended as a faster alternative to issuing a `(delete …)` for
1275    /// every row of the function: it drops the backing row storage in
1276    /// O(1)-in-row-count time, rather than O(n) per-row teardown. Any pending
1277    /// staged inserts/removes for this function are dropped as part of the
1278    /// clear, so callers that have staged updates they want to land first
1279    /// should arrange for those to be flushed beforehand.
1280    ///
1281    /// Cached indexes and subsets that reference this table are invalidated by
1282    /// a generation bump and are lazily rebuilt against the now-empty table on
1283    /// next access.
1284    ///
1285    /// Raises an error if the function does not exist.
1286    pub fn clear_function(&mut self, func_name: &str) -> Result<(), Error> {
1287        let backend_id = self
1288            .functions
1289            .get(func_name)
1290            .ok_or_else(|| TypeError::UnboundFunction(func_name.to_string(), span!()))?
1291            .backend_id;
1292        self.backend.clear_table(backend_id);
1293        Ok(())
1294    }
1295
1296    /// Evaluates an expression, returns the sort of the expression and the evaluation result.
1297    pub fn eval_expr(&mut self, expr: &Expr) -> Result<(ArcSort, Value), Error> {
1298        let span = expr.span();
1299        let command = Command::Action(Action::Expr(span.clone(), expr.clone()));
1300        let resolved = self.resolve_command(command)?;
1301        if self.are_proofs_enabled() {
1302            self.proof_check_program
1303                .extend(resolved.desugared_before_proofs);
1304        }
1305        let resolved_commands = resolved.desugared;
1306
1307        assert_eq!(resolved_commands.len(), 1);
1308        let resolved_command = resolved_commands.into_iter().next().unwrap();
1309        let resolved_expr = match resolved_command {
1310            ResolvedNCommand::CoreAction(ResolvedAction::Expr(_, resolved_expr)) => resolved_expr,
1311            _ => unreachable!(),
1312        };
1313        let sort = resolved_expr.output_type();
1314        let value = self.eval_resolved_expr(span, &resolved_expr)?;
1315        Ok((sort, value))
1316    }
1317
1318    /// Typecheck an expression under explicit local bindings, an expected
1319    /// output sort, and a primitive call context.
1320    ///
1321    /// `bindings` contains the local variables in scope while resolving `expr`.
1322    /// Each tuple is `(name, span, sort)`, where `span` is used for diagnostics
1323    /// tied to that binding. `output_sort` constrains overload resolution and
1324    /// output-type inference for the expression. Global references are rewritten
1325    /// into the same zero-argument function calls used during command execution.
1326    /// `context` should match the runtime context where the resolved expression
1327    /// will be evaluated.
1328    pub fn typecheck_expr_with_bindings_and_output(
1329        &mut self,
1330        expr: &Expr,
1331        bindings: &[(String, Span, ArcSort)],
1332        output_sort: ArcSort,
1333        context: Context,
1334    ) -> Result<ResolvedExpr, TypeError> {
1335        let mut binding_map = IndexMap::default();
1336        binding_map.reserve(bindings.len());
1337        for (name, span, sort) in bindings {
1338            if binding_map
1339                .insert(name.as_str(), (span.clone(), sort.clone()))
1340                .is_some()
1341            {
1342                return Err(TypeError::AlreadyDefined(name.clone(), span.clone()));
1343            }
1344        }
1345        let resolved = self.type_info.typecheck_expr_with_output(
1346            &mut self.parser.symbol_gen,
1347            expr,
1348            &binding_map,
1349            output_sort,
1350            context,
1351        )?;
1352        Ok(remove_globals::remove_globals_expr(resolved))
1353    }
1354
1355    /// Replace literal `(unstable-fn "...")` targets with hidden evaluator bindings.
1356    ///
1357    /// The returned expression must be evaluated with the returned bindings in
1358    /// scope before any caller-supplied local bindings. This lets direct
1359    /// execution-state evaluation use the same hidden `ResolvedFunction` value
1360    /// that backend rule lowering injects for `unstable-fn`.
1361    ///
1362    /// For example, a resolved body like `(unstable-app (unstable-fn "f") _0)`
1363    /// cannot evaluate the string literal `"f"` directly. This helper replaces
1364    /// the `(unstable-fn "f")` sub-expression with a fresh hidden variable and
1365    /// returns a binding from that variable to the prepared function value.
1366    pub fn prepare_unstable_fn_targets_for_eval(
1367        &mut self,
1368        expr: &ResolvedExpr,
1369    ) -> Result<(ResolvedExpr, Vec<(String, Value)>), Error> {
1370        let mut bindings = Vec::new();
1371        let expr = self.prepare_unstable_fn_targets_for_eval_inner(expr, &mut bindings)?;
1372        Ok((expr, bindings))
1373    }
1374
1375    fn prepare_unstable_fn_targets_for_eval_inner(
1376        &mut self,
1377        expr: &ResolvedExpr,
1378        bindings: &mut Vec<(String, Value)>,
1379    ) -> Result<ResolvedExpr, Error> {
1380        match expr {
1381            ResolvedExpr::Lit(..) | ResolvedExpr::Var(..) => Ok(expr.clone()),
1382            ResolvedExpr::Call(span, resolved_call, children) => {
1383                if let ResolvedCall::Primitive(prim) = resolved_call
1384                    && prim.name() == "unstable-fn"
1385                {
1386                    let Some(ResolvedExpr::Lit(target_span, Literal::String(name))) =
1387                        children.first()
1388                    else {
1389                        return Err(Error::BackendError(format!(
1390                            "{}\nunstable-fn requires a literal string function name",
1391                            children
1392                                .first()
1393                                .map(ResolvedExpr::span)
1394                                .unwrap_or_else(|| Span::Panic)
1395                        )));
1396                    };
1397                    let panic_id = self.backend.new_panic(format!(
1398                        "unstable-fn over `{name}` was applied in a context where its wrapped \
1399                         function is not valid for this call site, if in a rule, add :naive."
1400                    ));
1401                    let resolved_function = resolve_function_container_target_with_context(
1402                        &self.backend,
1403                        &self.functions,
1404                        &self.type_info,
1405                        name,
1406                        prim,
1407                        panic_id,
1408                    )?;
1409                    let fn_value = self.backend.base_values().get(resolved_function);
1410                    let binding_name = self.parser.symbol_gen.fresh("unstable_fn_target");
1411                    bindings.push((binding_name.clone(), fn_value));
1412                    let mut prepared_children = Vec::with_capacity(children.len());
1413                    prepared_children.push(ResolvedExpr::Var(
1414                        target_span.clone(),
1415                        ResolvedVar {
1416                            name: binding_name,
1417                            sort: children[0].output_type(),
1418                            is_global_ref: false,
1419                        },
1420                    ));
1421                    for child in &children[1..] {
1422                        prepared_children.push(
1423                            self.prepare_unstable_fn_targets_for_eval_inner(child, bindings)?,
1424                        );
1425                    }
1426                    return Ok(ResolvedExpr::Call(
1427                        span.clone(),
1428                        resolved_call.clone(),
1429                        prepared_children,
1430                    ));
1431                }
1432
1433                let prepared_children = children
1434                    .iter()
1435                    .map(|child| self.prepare_unstable_fn_targets_for_eval_inner(child, bindings))
1436                    .collect::<Result<Vec<_>, _>>()?;
1437                Ok(ResolvedExpr::Call(
1438                    span.clone(),
1439                    resolved_call.clone(),
1440                    prepared_children,
1441                ))
1442            }
1443        }
1444    }
1445
1446    fn eval_resolved_expr(&mut self, span: Span, expr: &ResolvedExpr) -> Result<Value, Error> {
1447        let unit_id = self.backend.base_values().get_ty::<()>();
1448        let unit_val = self.backend.base_values().get(());
1449
1450        let result: egglog_bridge::SideChannel<Value> = Default::default();
1451        let result_ref = result.clone();
1452        let ext_id = self
1453            .backend
1454            .register_external_func(Box::new(make_external_func(move |_es, vals| {
1455                debug_assert!(vals.len() == 1);
1456                *result_ref.lock().unwrap() = Some(vals[0]);
1457                Some(unit_val)
1458            })));
1459
1460        let mut translator = BackendRule::new(
1461            self.backend.new_rule("eval_resolved_expr", false),
1462            &self.functions,
1463            &self.type_info,
1464            true, // global action: Read/Full contexts (may read the DB)
1465        );
1466
1467        let result_var = ResolvedVar {
1468            name: self.parser.symbol_gen.fresh("eval_resolved_expr"),
1469            sort: expr.output_type(),
1470            is_global_ref: false,
1471        };
1472        let actions = ResolvedActions::singleton(ResolvedAction::Let(
1473            span.clone(),
1474            result_var.clone(),
1475            expr.clone(),
1476        ));
1477        let mut binding = IndexSet::default();
1478        let mut ctx = CoreActionContext::new(
1479            &self.type_info,
1480            &mut binding,
1481            &mut self.parser.symbol_gen,
1482            self.proof_state.original_typechecking.is_none(),
1483        );
1484        let actions = actions.to_core_actions(&mut ctx)?.0;
1485        translator.actions(&actions)?;
1486
1487        let arg = translator.entry(&ResolvedAtomTerm::Var(span.clone(), result_var));
1488        translator.rb.call_external_func(
1489            ext_id,
1490            &[arg],
1491            egglog_bridge::ColumnTy::Base(unit_id),
1492            || "this function will never panic".to_string(),
1493        );
1494
1495        let id = translator.build();
1496        let rule_result = self.backend.run_rules(&[id]);
1497        self.backend.free_rule(id);
1498        self.backend.free_external_func(ext_id);
1499        let _ = rule_result.map_err(|e| {
1500            Error::BackendError(format!("Failed to evaluate expression '{expr}': {e}"))
1501        })?;
1502
1503        let result = result.lock().unwrap().unwrap();
1504        Ok(result)
1505    }
1506
1507    fn add_combined_ruleset(&mut self, name: String, rulesets: Vec<String>) {
1508        match self.rulesets.entry(name.clone()) {
1509            Entry::Occupied(_) => panic!("Ruleset '{name}' was already present"),
1510            Entry::Vacant(e) => e.insert(Ruleset::Combined(rulesets)),
1511        };
1512    }
1513
1514    fn add_ruleset(&mut self, name: String) {
1515        match self.rulesets.entry(name.clone()) {
1516            Entry::Occupied(_) => panic!("Ruleset '{name}' was already present"),
1517            Entry::Vacant(e) => e.insert(Ruleset::Rules(Default::default())),
1518        };
1519    }
1520
1521    fn check_facts(&mut self, span: &Span, facts: &[ResolvedFact]) -> Result<(), Error> {
1522        let fresh_name = self.parser.symbol_gen.fresh("check_facts");
1523        let fresh_ruleset = self.parser.symbol_gen.fresh("check_facts_ruleset");
1524        let rule = ast::ResolvedRule {
1525            span: span.clone(),
1526            head: ResolvedActions::default(),
1527            body: facts.to_vec(),
1528            name: fresh_name.clone(),
1529            ruleset: fresh_ruleset.clone(),
1530            eval_mode: RuleEvalMode::default(),
1531            no_decomp: false,
1532            include_subsumed: false,
1533        };
1534        let core_rule = rule.to_canonicalized_core_rule(
1535            &self.type_info,
1536            &mut self.parser.symbol_gen,
1537            self.proof_state.original_typechecking.is_none(),
1538        )?;
1539        let query = core_rule.body;
1540
1541        let ext_sc = egglog_bridge::SideChannel::default();
1542        let ext_sc_ref = ext_sc.clone();
1543        let ext_id = self
1544            .backend
1545            .register_external_func(Box::new(make_external_func(move |_, _| {
1546                *ext_sc_ref.lock().unwrap() = Some(());
1547                Some(Value::new_const(0))
1548            })));
1549
1550        let mut translator = BackendRule::new(
1551            self.backend.new_rule("check_facts", false),
1552            &self.functions,
1553            &self.type_info,
1554            true, // global query: Read context (may read the DB)
1555        );
1556        translator.query(&query, true);
1557        translator
1558            .rb
1559            .call_external_func(ext_id, &[], egglog_bridge::ColumnTy::Id, || {
1560                "this function will never panic".to_string()
1561            });
1562        let id = translator.build();
1563        let run_result = self.backend.run_rules(&[id]);
1564        self.backend.free_rule(id);
1565        self.backend.free_external_func(ext_id);
1566        run_result.map_err(|e| Error::BackendError(e.to_string()))?;
1567
1568        let ext_sc_val = ext_sc.lock().unwrap().take();
1569        let matched = matches!(ext_sc_val, Some(()));
1570
1571        if !matched {
1572            Err(Error::CheckError(
1573                facts.iter().map(|f| f.clone().make_unresolved()).collect(),
1574                span.clone(),
1575            ))
1576        } else {
1577            Ok(())
1578        }
1579    }
1580
1581    fn run_command(&mut self, command: ResolvedNCommand) -> Result<Vec<CommandOutput>, Error> {
1582        match command {
1583            // Sorts are already declared during typechecking
1584            ResolvedNCommand::Sort {
1585                name,
1586                uf,
1587                proof_func,
1588                proof_constructors,
1589                ..
1590            } => {
1591                // Restore the sort's UF tables into proof_state: the constructor
1592                // for extraction's `find_canonical`, the index for container
1593                // rebuild's leader lookups.
1594                if let Some((uf_ctor, uf_index)) = uf {
1595                    self.proof_state.uf_parent.insert(name.clone(), uf_ctor);
1596                    if let Some(uf_index) = uf_index {
1597                        self.proof_state.uf_function.insert(name.clone(), uf_index);
1598                    }
1599                }
1600                // If the sort has a :internal-proof-func field, store the mapping for proof lookup.
1601                // This annotation is set by proof instrumentation and consumed here.
1602                if let Some(proof_func_name) = proof_func {
1603                    self.proof_state
1604                        .proof_func_parent
1605                        .insert(name.clone(), proof_func_name);
1606                }
1607                // The Proof sort's :internal-proof-names records the global proof
1608                // constructors; restore them so container rebuild can recover them.
1609                if let Some(pc) = proof_constructors {
1610                    let names = &mut self.proof_state.proof_names;
1611                    names.proof_datatype = name.clone();
1612                    names.congr_constructor = pc.congr;
1613                    names.eq_trans_constructor = pc.trans;
1614                    names.eq_sym_constructor = pc.sym;
1615                    names.container_normalize_constructor = pc.normalize;
1616                }
1617                log::info!("Declared sort {name}.")
1618            }
1619            ResolvedNCommand::Function(fdecl) => {
1620                self.declare_function(&fdecl)?;
1621                log::info!("Declared {} {}.", fdecl.subtype, fdecl.name)
1622            }
1623            ResolvedNCommand::AddRuleset(_span, name) => {
1624                self.add_ruleset(name.clone());
1625                log::info!("Declared ruleset {name}.");
1626            }
1627            ResolvedNCommand::UnstableCombinedRuleset(_span, name, others) => {
1628                self.add_combined_ruleset(name.clone(), others);
1629                log::info!("Declared ruleset {name}.");
1630            }
1631            ResolvedNCommand::NormRule { rule } => {
1632                let name = rule.name.clone();
1633                self.add_rule(rule)?;
1634                log::info!("Declared rule {name}.")
1635            }
1636            ResolvedNCommand::RunSchedule(sched) => {
1637                let report = self.run_schedule(&sched)?;
1638                log::info!("Ran schedule {sched}.");
1639                log::info!("Report: {report}");
1640                self.overall_run_report.union(report.clone());
1641                return Ok(vec![CommandOutput::RunSchedule(report)]);
1642            }
1643            ResolvedNCommand::PrintOverallStatistics(span, file) => match file {
1644                None => {
1645                    log::info!("Printed overall statistics");
1646                    return Ok(vec![CommandOutput::OverallStatistics(
1647                        self.overall_run_report.clone(),
1648                    )]);
1649                }
1650                Some(path) => {
1651                    let mut file = std::fs::File::create(&path)
1652                        .map_err(|e| Error::IoError(path.clone().into(), e, span.clone()))?;
1653                    log::info!("Printed overall statistics to json file {path}");
1654
1655                    serde_json::to_writer(&mut file, &self.overall_run_report)
1656                        .expect("error serializing to json");
1657                }
1658            },
1659            ResolvedNCommand::Check(span, facts) => {
1660                self.check_facts(&span, &facts)?;
1661                log::info!("Checked fact {facts:?}.");
1662            }
1663            ResolvedNCommand::CoreAction(action) => match &action {
1664                ResolvedAction::Let(_, name, contents) => {
1665                    panic!("Globals should have been desugared away: {name} = {contents}")
1666                }
1667                _ => {
1668                    self.eval_actions(&ResolvedActions::new(vec![action.clone()]))?;
1669                }
1670            },
1671            ResolvedNCommand::Extract(span, expr, variants) => {
1672                let sort = expr.output_type();
1673
1674                let x = self.eval_resolved_expr(span.clone(), &expr)?;
1675                let n = self.eval_resolved_expr(span, &variants)?;
1676                let n: i64 = self.backend.base_values().unwrap(n);
1677
1678                let mut termdag = TermDag::default();
1679
1680                let extractor = Extractor::compute_costs_from_rootsorts(
1681                    Some(vec![sort]),
1682                    self,
1683                    TreeAdditiveCostModel::default(),
1684                );
1685                return if n == 0 {
1686                    if let Some((cost, term)) = extractor.extract_best(self, &mut termdag, x) {
1687                        // dont turn termdag into a string if we have messages disabled for performance reasons
1688                        if log_enabled!(Level::Info) {
1689                            log::info!("extracted with cost {cost}: {}", termdag.to_string(term));
1690                        }
1691                        Ok(vec![CommandOutput::ExtractBest(termdag, cost, term)])
1692                    } else {
1693                        Err(Error::ExtractError(
1694                            "Unable to find any valid extraction (likely due to subsume or delete)"
1695                                .to_string(),
1696                        ))
1697                    }
1698                } else {
1699                    if n < 0 {
1700                        panic!("Cannot extract negative number of variants");
1701                    }
1702                    let terms: Vec<TermId> = extractor
1703                        .extract_variants(self, &mut termdag, x, n as usize)
1704                        .iter()
1705                        .map(|e| e.1)
1706                        .collect();
1707                    if log_enabled!(Level::Info) {
1708                        let expr_str = expr.to_string();
1709                        log::info!("extracted {} variants for {expr_str}", terms.len());
1710                    }
1711                    Ok(vec![CommandOutput::ExtractVariants(termdag, terms)])
1712                };
1713            }
1714            ResolvedNCommand::Push(n) => {
1715                (0..n).for_each(|_| self.push());
1716                log::info!("Pushed {n} levels.")
1717            }
1718            ResolvedNCommand::Pop(span, n) => {
1719                for _ in 0..n {
1720                    self.pop().map_err(|err| {
1721                        if let Error::Pop(_) = err {
1722                            Error::Pop(span.clone())
1723                        } else {
1724                            err
1725                        }
1726                    })?;
1727                }
1728                log::info!("Popped {n} levels.")
1729            }
1730            ResolvedNCommand::PrintFunction(span, f, n, file, mode) => {
1731                let file = file
1732                    .map(|file| {
1733                        std::fs::File::create(&file)
1734                            .map_err(|e| Error::IoError(file.into(), e, span.clone()))
1735                    })
1736                    .transpose()?;
1737                return self
1738                    .print_function(&f, n, file, mode)
1739                    .map_err(|e| match e {
1740                        Error::TypeError(TypeError::UnboundFunction(f, _)) => {
1741                            Error::TypeError(TypeError::UnboundFunction(f, span.clone()))
1742                        }
1743                        // This case is currently impossible
1744                        _ => e,
1745                    })
1746                    .map(|opt| opt.into_iter().collect());
1747            }
1748            ResolvedNCommand::PrintSize(span, f) => {
1749                let res = self.print_size(f.as_deref()).map_err(|e| match e {
1750                    Error::TypeError(TypeError::UnboundFunction(f, _)) => {
1751                        Error::TypeError(TypeError::UnboundFunction(f, span.clone()))
1752                    }
1753                    // This case is currently impossible
1754                    _ => e,
1755                })?;
1756                return Ok(vec![res]);
1757            }
1758            ResolvedNCommand::Fail(span, c) => {
1759                let result = self.run_command(*c);
1760                if let Err(e) = result {
1761                    log::info!("Command failed as expected: {e}");
1762                } else {
1763                    return Err(Error::ExpectFail(span));
1764                }
1765            }
1766            ResolvedNCommand::Input {
1767                span: _,
1768                name,
1769                file,
1770            } => {
1771                self.input_file(&name, file)?;
1772            }
1773            ResolvedNCommand::Output { span, file, exprs } => {
1774                let mut filename = self.fact_directory.clone().unwrap_or_default();
1775                filename.push(file.as_str());
1776                // append to file
1777                let mut f = File::options()
1778                    .append(true)
1779                    .create(true)
1780                    .open(&filename)
1781                    .map_err(|e| Error::IoError(filename.clone(), e, span.clone()))?;
1782
1783                let extractor = Extractor::compute_costs_from_rootsorts(
1784                    None,
1785                    self,
1786                    TreeAdditiveCostModel::default(),
1787                );
1788                let mut termdag: TermDag = Default::default();
1789
1790                use std::io::Write;
1791                for expr in exprs {
1792                    let value = self.eval_resolved_expr(span.clone(), &expr)?;
1793                    let expr_type = expr.output_type();
1794
1795                    let term = extractor
1796                        .extract_best_with_sort(self, &mut termdag, value, expr_type)
1797                        .unwrap()
1798                        .1;
1799                    writeln!(f, "{}", termdag.to_string(term))
1800                        .map_err(|e| Error::IoError(filename.clone(), e, span.clone()))?;
1801                }
1802
1803                log::info!("Output to '{filename:?}'.")
1804            }
1805            ResolvedNCommand::UserDefined(_span, name, exprs) => {
1806                let command = self
1807                    .commands
1808                    .get(&name)
1809                    .ok_or_else(|| {
1810                        NotFoundError(format!("Unrecognized user-defined command: {name}"))
1811                    })?
1812                    .clone();
1813                return command.update(self, &exprs);
1814            }
1815
1816            ResolvedNCommand::ProveExists(span, resolved_call) => {
1817                let mut instrument = ProofInstrumentor { egraph: self };
1818                let (proof_store, proof_id) =
1819                    instrument
1820                        .prove_exists(&resolved_call)
1821                        .map_err(|error| Error::ProofError {
1822                            span: span.clone(),
1823                            error,
1824                        })?;
1825                return Ok(vec![CommandOutput::ProveExists {
1826                    proof_store,
1827                    proof_id,
1828                }]);
1829            }
1830        };
1831
1832        Ok(vec![])
1833    }
1834
1835    fn input_file(&mut self, func_name: &str, file: String) -> Result<(), Error> {
1836        let function_type = self
1837            .type_info
1838            .get_func_type(func_name)
1839            .unwrap_or_else(|| panic!("Unrecognized function name {func_name}"));
1840        let func = self.functions.get_mut(func_name).unwrap();
1841
1842        let mut filename = self.fact_directory.clone().unwrap_or_default();
1843        filename.push(file.as_str());
1844
1845        // check that the function uses supported types
1846
1847        for t in &func.schema.input {
1848            match t.name() {
1849                "i64" | "f64" | "String" => {}
1850                s => panic!("Unsupported type {s} for input"),
1851            }
1852        }
1853
1854        if function_type.subtype != FunctionSubtype::Constructor {
1855            match func.schema.output.name() {
1856                "i64" | "String" | "Unit" => {}
1857                s => panic!("Unsupported type {s} for input"),
1858            }
1859        }
1860
1861        log::info!("Opening file '{filename:?}'...");
1862        let mut f = File::open(filename).unwrap();
1863        let mut contents = String::new();
1864        f.read_to_string(&mut contents).unwrap();
1865
1866        // Can also do a row-major Vec<Value>
1867        let mut parsed_contents: Vec<Vec<Value>> = Vec::with_capacity(contents.lines().count());
1868
1869        let mut row_schema = func.schema.input.clone();
1870        if function_type.subtype == FunctionSubtype::Custom {
1871            row_schema.push(func.schema.output.clone());
1872        }
1873
1874        log::debug!("{row_schema:?}");
1875
1876        let unit_val = self.backend.base_values().get(());
1877
1878        for line in contents.lines() {
1879            let mut it = line.split('\t').map(|s| s.trim());
1880
1881            let mut row: Vec<Value> = Vec::with_capacity(row_schema.len());
1882
1883            for sort in row_schema.iter() {
1884                if let Some(raw) = it.next() {
1885                    let val = match sort.name() {
1886                        "i64" => {
1887                            if let Ok(i) = raw.parse::<i64>() {
1888                                self.backend.base_values().get(i)
1889                            } else {
1890                                return Err(Error::InputFileFormatError(file));
1891                            }
1892                        }
1893                        "f64" => {
1894                            if let Ok(f) = raw.parse::<f64>() {
1895                                self.backend
1896                                    .base_values()
1897                                    .get::<F>(core_relations::Boxed::new(f.into()))
1898                            } else {
1899                                return Err(Error::InputFileFormatError(file));
1900                            }
1901                        }
1902                        "String" => self.backend.base_values().get::<S>(raw.to_string().into()),
1903                        "Unit" => unit_val,
1904                        _ => panic!("Unreachable"),
1905                    };
1906                    row.push(val);
1907                } else {
1908                    break;
1909                }
1910            }
1911
1912            if row.is_empty() {
1913                continue;
1914            }
1915
1916            if row.len() != row_schema.len() || it.next().is_some() {
1917                return Err(Error::InputFileFormatError(file));
1918            }
1919
1920            parsed_contents.push(row);
1921        }
1922
1923        log::debug!("Successfully loaded file.");
1924
1925        let num_facts = parsed_contents.len();
1926
1927        let table_action = egglog_bridge::TableAction::new(&self.backend, func.backend_id);
1928
1929        if function_type.subtype != FunctionSubtype::Constructor {
1930            self.backend.with_execution_state(|es| {
1931                for row in parsed_contents.iter() {
1932                    table_action.insert(es, row.iter().copied());
1933                }
1934                Some(unit_val)
1935            });
1936        } else {
1937            self.backend.with_execution_state(|es| {
1938                for row in parsed_contents.iter() {
1939                    // Constructor semantics: mint a fresh eclass id for
1940                    // each missing key.
1941                    table_action.lookup_or_insert(es, row);
1942                }
1943                Some(unit_val)
1944            });
1945        }
1946
1947        self.backend.flush_updates();
1948
1949        log::info!("Read {num_facts} facts into {func_name} from '{file}'.");
1950        Ok(())
1951    }
1952
1953    /// Returns true if proofs are enabled.
1954    pub fn are_proofs_enabled(&self) -> bool {
1955        self.proof_state.proofs_enabled
1956    }
1957
1958    fn resolve_command_before_proofs(
1959        &mut self,
1960        command: Command,
1961    ) -> Result<Vec<ResolvedNCommand>, Error> {
1962        let desugared = desugar_command(command, &mut self.parser, self.proof_state.proof_testing)?;
1963        if let Some(original_typechecking) = self.proof_state.original_typechecking.as_mut() {
1964            // Typecheck using the original egraph
1965            // TODO this is ugly- we don't need an entire e-graph just for type information.
1966            let typechecked = original_typechecking.typecheck_program(&desugared)?;
1967
1968            for command in &typechecked {
1969                if let Err(reason) = command_supports_proof_encoding(
1970                    &command.to_command(),
1971                    &original_typechecking.type_info,
1972                ) {
1973                    let command_text = format!("{}", command.to_command());
1974                    return Err(Error::UnsupportedProofCommand {
1975                        command: command_text,
1976                        reason,
1977                    });
1978                }
1979            }
1980
1981            Ok(proof_form(typechecked, &mut self.parser.symbol_gen))
1982        } else {
1983            let mut typechecked = self.typecheck_program(&desugared)?;
1984
1985            typechecked = remove_globals::remove_globals(typechecked, &mut self.parser.symbol_gen);
1986            for command in &typechecked {
1987                self.names.check_shadowing(command)?;
1988            }
1989            Ok(typechecked)
1990        }
1991    }
1992
1993    /// Desugars, typechecks, and removes globals from a single [`Command`].
1994    /// Leverages previous type information in the [`EGraph`] to do so, adding new type information.
1995    /// When will_run is true, adds to `desugared_commands_run_so_far`, which is used for proof checking.
1996    fn resolve_command(&mut self, command: Command) -> Result<ResolvedNCommands, Error> {
1997        let resolved_before_proofs = self.resolve_command_before_proofs(command)?;
1998
1999        // Add term encoding when it is enabled
2000        if self.proof_state.original_typechecking.is_none() {
2001            Ok(ResolvedNCommands {
2002                desugared: resolved_before_proofs,
2003                desugared_before_proofs: vec![],
2004            })
2005        } else {
2006            // Now remove globals for actual execution (but NOT from desugared_commands)
2007            let typechecked_no_globals = proof_global_remover::remove_globals(
2008                resolved_before_proofs.clone(),
2009                &mut self.parser.symbol_gen,
2010            );
2011            for command in &typechecked_no_globals {
2012                self.names.check_shadowing(command)?;
2013            }
2014
2015            let term_encoding_added =
2016                ProofInstrumentor::add_term_encoding(self, typechecked_no_globals);
2017            let mut new_typechecked = vec![];
2018            for new_cmd in term_encoding_added {
2019                let desugared =
2020                    desugar_command(new_cmd, &mut self.parser, self.proof_state.proof_testing)?;
2021                for cmd in &desugared {
2022                    log::trace!("Desugared term encoding: {}", cmd.to_command());
2023                }
2024
2025                // Now typecheck using self, adding term type information.
2026                let desugared_typechecked = self.typecheck_program(&desugared)?;
2027                // remove globals again, but this time allow primitive globals
2028                let desugared_typechecked = remove_globals::remove_globals(
2029                    desugared_typechecked,
2030                    &mut self.parser.symbol_gen,
2031                );
2032
2033                new_typechecked.extend(desugared_typechecked);
2034            }
2035            Ok(ResolvedNCommands {
2036                desugared: new_typechecked,
2037                desugared_before_proofs: resolved_before_proofs,
2038            })
2039        }
2040    }
2041
2042    /// Run a program, returning the desugared outputs as well as the CommandOutputs.
2043    /// Can optionally not run the commands, just adding type information.
2044    fn process_program_internal(
2045        &mut self,
2046        program: Vec<Command>,
2047        run_commands: bool,
2048    ) -> Result<ResolvedNCommandsWithOutput, Error> {
2049        let mut outputs = Vec::new();
2050        let mut desugared_before_proofs = Vec::new();
2051        let mut desugared = Vec::new();
2052
2053        for before_expanded_command in program {
2054            // First do user-provided macro expansion for this command,
2055            // which may rely on type information from previous commands.
2056            let macro_type_info = self
2057                .proof_state
2058                .original_typechecking
2059                .as_ref()
2060                .map(|egraph| &egraph.type_info)
2061                .unwrap_or(&self.type_info);
2062            let macro_expanded = self.command_macros.apply(
2063                before_expanded_command,
2064                &mut self.parser.symbol_gen,
2065                macro_type_info,
2066            )?;
2067
2068            for command in macro_expanded {
2069                // handle include specially- we keep them as-is for desugaring
2070                if let Command::Include(span, file) = &command {
2071                    let s = std::fs::read_to_string(file)
2072                        .map_err(|e| Error::IoError(file.clone().into(), e, span.clone()))?;
2073                    let included_program = self
2074                        .parser
2075                        .get_program_from_string(Some(file.clone()), &s)?;
2076                    // run program internal on these include commands
2077                    let resolved = self.process_program_internal(included_program, run_commands)?;
2078                    outputs.extend(resolved.outputs);
2079                    desugared.extend(resolved.resolved);
2080                    desugared_before_proofs.extend(resolved.resolved_before_proofs);
2081                } else {
2082                    let resolved = self.resolve_command(command)?;
2083                    if run_commands && self.are_proofs_enabled() {
2084                        self.proof_check_program
2085                            .extend(resolved.desugared_before_proofs.clone());
2086                    }
2087
2088                    desugared_before_proofs.extend(resolved.desugared_before_proofs);
2089                    desugared.extend(resolved.desugared.clone());
2090
2091                    for processed in resolved.desugared {
2092                        // even in desugar mode we still run push and pop
2093                        if run_commands
2094                            || matches!(
2095                                processed,
2096                                ResolvedNCommand::Push(_) | ResolvedNCommand::Pop(_, _)
2097                            )
2098                        {
2099                            let result = self.run_command(processed)?;
2100                            outputs.extend(result);
2101                        }
2102                    }
2103                }
2104            }
2105        }
2106
2107        Ok(ResolvedNCommandsWithOutput {
2108            outputs,
2109            resolved_before_proofs: desugared_before_proofs,
2110            resolved: desugared,
2111        })
2112    }
2113
2114    /// Run a program, represented as an AST.
2115    /// Return a list of messages.
2116    pub fn run_program(&mut self, program: Vec<Command>) -> Result<Vec<CommandOutput>, Error> {
2117        let res = self.process_program_internal(program, true)?;
2118        Ok(res.outputs)
2119    }
2120
2121    /// Resolves an egglog program by parsing, typechecking, and desugaring each command.
2122    /// Outputs a new egglog program without any syntactic sugar, either user provided ([`CommandMacro`]) or built-in (e.g., `rewrite` commands).
2123    /// Also removes globals from the program by replacing with new constructors.
2124    pub fn resolve_program(
2125        &mut self,
2126        filename: Option<String>,
2127        input: &str,
2128    ) -> Result<Vec<ResolvedCommand>, Error> {
2129        let parsed = self.parser.get_program_from_string(filename, input)?;
2130        let res = self.process_program_internal(parsed, false)?;
2131        Ok(res.resolved.into_iter().map(|c| c.to_command()).collect())
2132    }
2133
2134    /// Takes a source program `input` and parses it into a list of [`Command`]s.
2135    pub fn parse_program(
2136        &mut self,
2137        filename: Option<String>,
2138        input: &str,
2139    ) -> Result<Vec<Command>, Error> {
2140        let parsed = self.parser.get_program_from_string(filename, input)?;
2141        Ok(parsed)
2142    }
2143
2144    /// Takes a source program `input`, parses it, runs it, and returns a list of messages.
2145    ///
2146    /// `filename` is an optional argument to indicate the source of
2147    /// the program for error reporting. If `filename` is `None`,
2148    /// a default name will be used.
2149    pub fn parse_and_run_program(
2150        &mut self,
2151        filename: Option<String>,
2152        input: &str,
2153    ) -> Result<Vec<CommandOutput>, Error> {
2154        let parsed = self.parser.get_program_from_string(filename, input)?;
2155        self.run_program(parsed)
2156    }
2157
2158    /// Get the number of tuples in the database.
2159    ///
2160    pub fn num_tuples(&self) -> usize {
2161        self.functions
2162            .values()
2163            .map(|f| self.backend.table_size(f.backend_id))
2164            .sum()
2165    }
2166
2167    /// Returns a sort based on the type.
2168    pub fn get_sort<S: Sort>(&self) -> Arc<S> {
2169        self.type_info.get_sort()
2170    }
2171
2172    /// Returns a sort that satisfies the type and predicate.
2173    pub fn get_sort_by<S: Sort>(&self, f: impl Fn(&Arc<S>) -> bool) -> Arc<S> {
2174        self.type_info.get_sort_by(f)
2175    }
2176
2177    /// Returns all sorts based on the type.
2178    pub fn get_sorts<S: Sort>(&self) -> Vec<Arc<S>> {
2179        self.type_info.get_sorts()
2180    }
2181
2182    /// Returns all sorts that satisfy the type and predicate.
2183    pub fn get_sorts_by<S: Sort>(&self, f: impl Fn(&Arc<S>) -> bool) -> Vec<Arc<S>> {
2184        self.type_info.get_sorts_by(f)
2185    }
2186
2187    /// Returns a sort based on the predicate.
2188    pub fn get_arcsort_by(&self, f: impl Fn(&ArcSort) -> bool) -> ArcSort {
2189        self.type_info.get_arcsort_by(f)
2190    }
2191
2192    /// Returns the unique sort whose runtime values have Rust type `T`.
2193    pub fn get_arcsort_for_value_type<T: 'static>(&self) -> ArcSort {
2194        self.type_info.get_arcsort_for_value_type::<T>()
2195    }
2196
2197    /// Returns all sorts that satisfy the predicate.
2198    pub fn get_arcsorts_by(&self, f: impl Fn(&ArcSort) -> bool) -> Vec<ArcSort> {
2199        self.type_info.get_arcsorts_by(f)
2200    }
2201
2202    /// Returns the sort with the given name if it exists.
2203    pub fn get_sort_by_name(&self, sym: &str) -> Option<&ArcSort> {
2204        self.type_info.get_sort_by_name(sym)
2205    }
2206
2207    /// Gets the overall run report and returns it.
2208    pub fn get_overall_run_report(&self) -> &RunReport {
2209        &self.overall_run_report
2210    }
2211
2212    /// Convert from an egglog value to a Rust type.
2213    /// This method assumes `x` belongs to sort `T`.
2214    pub fn value_to_base<T: BaseValue>(&self, x: Value) -> T {
2215        self.backend.base_values().unwrap::<T>(x)
2216    }
2217
2218    /// Convert from a Rust type to an egglog value.
2219    pub fn base_to_value<T: BaseValue>(&self, x: T) -> Value {
2220        self.backend.base_values().get::<T>(x)
2221    }
2222
2223    /// Convert from an egglog value to a reference of a Rust container type.
2224    ///
2225    /// Returns `None` if the value cannot be converted to the requested container type.
2226    ///
2227    /// Warning: The return type of this function may contain lock guards.
2228    /// Attempts to modify the contents of the containers database may deadlock if the given guard has not been dropped.
2229    pub fn value_to_container<T: ContainerValue>(
2230        &self,
2231        x: Value,
2232    ) -> Option<impl Deref<Target = T>> {
2233        self.backend.container_values().get_val::<T>(x)
2234    }
2235
2236    /// Convert from a Rust container type to an egglog value.
2237    pub fn container_to_value<T: ContainerValue>(&mut self, x: T) -> Value {
2238        self.backend.with_execution_state(|state| {
2239            self.backend.container_values().register_val::<T>(x, state)
2240        })
2241    }
2242
2243    /// Get the size of a function in the e-graph.
2244    ///
2245    /// `panics` if the function does not exist.
2246    pub fn get_size(&self, func: &str) -> usize {
2247        let function_id = self.functions.get(func).unwrap().backend_id;
2248        self.backend.table_size(function_id)
2249    }
2250
2251    /// Get a function by name.
2252    ///
2253    /// Returns `None` if the function does not exist.
2254    pub fn get_function(&self, name: &str) -> Option<&Function> {
2255        self.functions.get(name)
2256    }
2257
2258    /// Returns `true` if a user-defined command with the given name is
2259    /// registered in this e-graph.
2260    pub fn has_command(&self, name: &str) -> bool {
2261        self.commands.contains_key(name)
2262    }
2263
2264    /// Invoke a registered user-defined command by name, passing the given
2265    /// unresolved expression arguments.
2266    ///
2267    /// This is equivalent to writing `(name args...)` at the top level, but
2268    /// callable directly from Rust.  Returns an error if no command with the given
2269    /// name is registered.
2270    pub fn run_user_defined_command(
2271        &mut self,
2272        name: &str,
2273        args: &[Expr],
2274    ) -> Result<Vec<CommandOutput>, Error> {
2275        self.run_command(ResolvedNCommand::UserDefined(
2276            span!(),
2277            name.to_string(),
2278            args.to_vec(),
2279        ))
2280    }
2281
2282    /// Set the report verbosity level for rule execution output.
2283    pub fn set_report_level(&mut self, level: ReportLevel) {
2284        self.backend.set_report_level(level);
2285    }
2286
2287    /// A basic method for dumping the state of the database to `log::info!`.
2288    ///
2289    /// For large tables, this is unlikely to give particularly useful output.
2290    pub fn dump_debug_info(&self) {
2291        self.backend.dump_debug_info();
2292    }
2293
2294    /// Run `f` with a [`FullState`] handle on this EGraph's database
2295    /// — the same handle a `:naive` rule's `add_rust_rule_full`
2296    /// callback receives. Use to drive name-indexed reads / writes
2297    /// (`fs.set`, `fs.add`, `fs.lookup`, `fs.eclass_of`,
2298    /// `fs.contains`, `fs.remove`, …) from outside a rule.
2299    ///
2300    /// # Flush semantics
2301    ///
2302    /// Pending writes flush once, **after** `f` returns. Two
2303    /// consequences:
2304    ///
2305    /// 1. A `set` / `add` / `remove` inside the closure is *not*
2306    ///    visible to a subsequent `lookup` / `contains` / `eclass_of`
2307    ///    in the **same** closure. Split write-then-read into separate
2308    ///    `update` calls.
2309    /// 2. Conversely, batching multiple writes in one closure is the
2310    ///    fast path — only one flush + rebuild happens, regardless of
2311    ///    how many writes occurred.
2312    /// 3. A closure that only reads (e.g. `lookup`, `constructor_enodes`)
2313    ///    stages nothing, so the flush is skipped entirely — a read
2314    ///    costs no more than a direct backend scan.
2315    ///
2316    /// # Example
2317    /// ```
2318    /// use egglog::prelude::*;
2319    /// let mut eg = EGraph::default();
2320    /// eg.parse_and_run_program(None, "(function f (i64) i64 :no-merge)")?;
2321    /// eg.update(|mut fs| fs.set("f", (1_i64,), 42_i64))?;
2322    /// let got = eg.update(|fs| fs.lookup("f", 1_i64))?;
2323    /// let got: Option<i64> = got.map(|v| eg.value_to_base::<i64>(v));
2324    /// assert_eq!(got, Some(42));
2325    /// # Ok::<(), egglog::Error>(())
2326    /// ```
2327    pub fn update<R>(
2328        &mut self,
2329        f: impl FnOnce(FullState<'_, '_>) -> Result<R, Error>,
2330    ) -> Result<R, Error> {
2331        if self.are_proofs_enabled() {
2332            return Err(Error::ProofsIncompatibleApi {
2333                api: "EGraph::update",
2334                reason: "writes inside the closure bypass the proof-encoding pipeline,\n\
2335                         so any rule derivations resting on them would be unverifiable.",
2336            });
2337        }
2338        self.update_unchecked(f)
2339    }
2340
2341    /// Internal version of [`EGraph::update`] without the proofs
2342    /// check. Used by proof-system internals that need to read the
2343    /// e-graph while the proof system itself is enabled.
2344    pub(crate) fn update_unchecked<R>(
2345        &mut self,
2346        f: impl FnOnce(FullState<'_, '_>) -> Result<R, Error>,
2347    ) -> Result<R, Error> {
2348        let registry = self.backend.action_registry().clone();
2349        let guard = registry.read().unwrap();
2350        let (result, changed) = self
2351            .backend
2352            .with_execution_state_tracked(|es| f(FullState::wrap(es, &guard, Context::Full)));
2353        drop(guard);
2354        // A read-only closure stages nothing, so `flush_updates` would only do
2355        // a no-op merge plus a spurious timestamp bump and rebuild check. Skip
2356        // it unless the closure actually wrote, keeping reads as cheap as a
2357        // direct backend scan.
2358        if changed {
2359            self.backend.flush_updates();
2360        }
2361        result
2362    }
2363
2364    /// Run a pattern query: bind the variables in `vars` against
2365    /// `facts` and return one [`HashMap`] per match, keyed by variable
2366    /// name. Values stay raw — convert via [`EGraph::value_to_base`].
2367    ///
2368    /// With zero vars, returns at most one empty map (so `.len()` is 1
2369    /// if the body matched, 0 if it didn't).
2370    pub fn query(
2371        &mut self,
2372        vars: &[(&str, ArcSort)],
2373        facts: ast::Facts<String, String>,
2374    ) -> Result<Vec<HashMap<String, Value>>, Error> {
2375        // Fail fast under proofs — otherwise the failure would
2376        // surface through `rust_rule`'s check below with a misleading
2377        // api: "rust_rule" in the error message.
2378        if self.are_proofs_enabled() {
2379            return Err(Error::ProofsIncompatibleApi {
2380                api: "EGraph::query",
2381                reason: "the underlying rust_rule callback has no proof-encoding validator,\n\
2382                         so query matches cannot be verified.",
2383            });
2384        }
2385        use std::sync::{Arc, Mutex};
2386        let names: Arc<[String]> = vars.iter().map(|(n, _)| (*n).to_owned()).collect();
2387        let results: Arc<Mutex<Vec<HashMap<String, Value>>>> = Arc::new(Mutex::new(Vec::new()));
2388        let results_weak = Arc::downgrade(&results);
2389        let names_for_cb = names.clone();
2390
2391        let ruleset = self.parser.symbol_gen.fresh("query_ruleset");
2392        prelude::add_ruleset(self, &ruleset)?;
2393        // From here on, we OWN the ruleset and the rule and have to
2394        // clean them up on every exit path. Run the rest in a closure
2395        // and tear down before propagating.
2396        let outcome = (|| -> Result<_, Error> {
2397            prelude::rust_rule(self, "query", &ruleset, vars, facts, move |_, values| {
2398                let arc = results_weak.upgrade().unwrap();
2399                let mut results = arc.lock().unwrap();
2400                let map: HashMap<String, Value> = names_for_cb
2401                    .iter()
2402                    .zip(values.iter().copied())
2403                    .map(|(n, v)| (n.clone(), v))
2404                    .collect();
2405                results.push(map);
2406                Some(())
2407            })?;
2408            prelude::run_ruleset(self, &ruleset)?;
2409            Ok(())
2410        })();
2411
2412        // Tear the temporary rule + ruleset down whether the body
2413        // succeeded or not.
2414        if let Some(Ruleset::Rules(rules)) = self.rulesets.swap_remove(&ruleset) {
2415            for (_, rule) in rules {
2416                self.backend.free_rule(rule.1);
2417            }
2418        }
2419        outcome?;
2420
2421        let Some(mutex) = Arc::into_inner(results) else {
2422            panic!("`results_weak` outlived the callback");
2423        };
2424        Ok(mutex.into_inner().unwrap())
2425    }
2426}
2427
2428pub use crate::api::{ApiError, FromValue, FromValues, IntoValue, IntoValues, RawValues};
2429
2430/// Build the runtime value backing a resolved `(unstable-fn name)` target.
2431///
2432/// For table-backed functions, this captures the table action that
2433/// `unstable-app` will call later. For primitive targets, this bakes one
2434/// dispatch id per runtime context so application can choose the entrypoint
2435/// matching the primitive body's current call-site context.
2436fn resolve_function_container_target_with_context(
2437    backend: &egglog_bridge::EGraph,
2438    functions: &IndexMap<String, Function>,
2439    type_info: &TypeInfo,
2440    name: &str,
2441    primitive: &core::SpecializedPrimitive,
2442    panic_id: ExternalFunctionId,
2443) -> Result<ResolvedFunction, Error> {
2444    let target_function = type_info
2445        .get_sorts::<FunctionSort>()
2446        .into_iter()
2447        .find(|function| function.name() == primitive.output().name())
2448        .ok_or_else(|| {
2449            Error::BackendError(format!(
2450                "`unstable-fn` output sort `{}` is not a function sort",
2451                primitive.output().name()
2452            ))
2453        })?;
2454
2455    let partial_arcsorts: Vec<_> = primitive.input().iter().skip(1).cloned().collect();
2456    let remaining_inputs = target_function.inputs();
2457    let output = target_function.output();
2458
2459    let id = if let Some(func) = functions.get(name) {
2460        let func_type = type_info
2461            .get_func_type(name)
2462            .ok_or_else(|| Error::BackendError(format!("No resolution for {name:?}")))?;
2463        let expected_inputs = partial_arcsorts
2464            .iter()
2465            .chain(remaining_inputs)
2466            .collect::<Vec<_>>();
2467        let inputs_match = func_type.input.len() == expected_inputs.len()
2468            && func_type
2469                .input
2470                .iter()
2471                .zip(&expected_inputs)
2472                .all(|(actual, expected)| actual.name() == expected.name());
2473        if !inputs_match || func_type.output.name() != output.name() {
2474            let expected_input_names = expected_inputs
2475                .iter()
2476                .map(|sort| sort.name())
2477                .collect::<Vec<_>>()
2478                .join(", ");
2479            let actual_input_names = func_type
2480                .input
2481                .iter()
2482                .map(|sort| sort.name())
2483                .collect::<Vec<_>>()
2484                .join(", ");
2485            return Err(Error::BackendError(format!(
2486                "function container lookup for `{name}` expected ({}) -> {}, found ({}) -> {}",
2487                expected_input_names,
2488                output.name(),
2489                actual_input_names,
2490                func_type.output.name(),
2491            )));
2492        }
2493
2494        let action = egglog_bridge::TableAction::new(backend, func.backend_id);
2495        match func_type.subtype {
2496            ast::FunctionSubtype::Constructor => ResolvedFunctionId::Constructor(action),
2497            ast::FunctionSubtype::Custom => ResolvedFunctionId::Function(action),
2498        }
2499    } else if let Some(primitives) = type_info.get_prims(name) {
2500        let signature: Vec<_> = partial_arcsorts
2501            .iter()
2502            .chain(remaining_inputs)
2503            .chain(once(&output))
2504            .cloned()
2505            .collect();
2506        let candidates: Vec<_> = primitives
2507            .iter()
2508            .filter(|primitive| primitive.accept(&signature, type_info))
2509            .collect();
2510        let mut context_ids = enum_map::EnumMap::from_fn(|_| None);
2511        for runtime_ctx in Context::ALL {
2512            let mut ids = candidates
2513                .iter()
2514                .filter_map(|primitive| primitive.context_ids[runtime_ctx]);
2515            // The first `next` finds the candidate for this runtime context;
2516            // the second detects whether there is more than one such candidate.
2517            match (ids.next(), ids.next()) {
2518                (None, _) => {}
2519                (Some(id), None) => context_ids[runtime_ctx] = Some(id),
2520                (Some(_), Some(_)) => {
2521                    return Err(Error::BackendError(format!(
2522                        "Ambiguous primitive resolution for {name:?} in unstable-fn context {runtime_ctx:?}"
2523                    )));
2524                }
2525            }
2526        }
2527        if !context_ids.iter().any(|(_, id)| id.is_some()) {
2528            let (output_sort, input_sorts) = signature
2529                .split_last()
2530                .expect("primitive signature should include an output sort");
2531            let input_names = input_sorts
2532                .iter()
2533                .map(|sort| sort.name())
2534                .collect::<Vec<_>>()
2535                .join(", ");
2536            return Err(Error::BackendError(format!(
2537                "no primitive overload matched expected signature for {name:?}: ({}) -> {}; \
2538                 context ids: {context_ids:?}",
2539                input_names,
2540                output_sort.name(),
2541            )));
2542        }
2543        ResolvedFunctionId::Primitive { context_ids }
2544    } else {
2545        return Err(Error::BackendError(format!("No resolution for {name:?}")));
2546    };
2547
2548    Ok(ResolvedFunction {
2549        id,
2550        partial_arcsorts,
2551        name: name.to_owned(),
2552        panic_id,
2553    })
2554}
2555
2556struct BackendRule<'a> {
2557    rb: egglog_bridge::RuleBuilder<'a>,
2558    entries: HashMap<core::ResolvedAtomTerm, QueryEntry>,
2559    functions: &'a IndexMap<String, Function>,
2560    type_info: &'a TypeInfo,
2561    /// Whether primitives may read the database. When true the per-phase
2562    /// [`crate::Context`] widens from `Pure`/`Write` to `Read`/`Full` (query
2563    /// gains reads, action gains reads on top of writes). True for `:naive` /
2564    /// `:unsafe-seminaive` rules and a non-seminaive EGraph.
2565    requires_read_context: bool,
2566}
2567
2568impl<'a> BackendRule<'a> {
2569    fn new(
2570        rb: egglog_bridge::RuleBuilder<'a>,
2571        functions: &'a IndexMap<String, Function>,
2572        type_info: &'a TypeInfo,
2573        requires_read_context: bool,
2574    ) -> BackendRule<'a> {
2575        BackendRule {
2576            rb,
2577            functions,
2578            type_info,
2579            requires_read_context,
2580            entries: Default::default(),
2581        }
2582    }
2583
2584    /// The [`crate::Context`] that applies when compiling
2585    /// primitives on the query side (LHS) of this rule. Under
2586    /// seminaive evaluation, queries are pure (no DB reads or
2587    /// writes); a `:naive` rule (or `eg.seminaive = false`) widens
2588    /// this to [`Context::Read`] so reads from primitives are
2589    /// admissible.
2590    fn query_context(&self) -> crate::Context {
2591        if self.requires_read_context {
2592            crate::Context::Read
2593        } else {
2594            crate::Context::Pure
2595        }
2596    }
2597
2598    /// The [`crate::Context`] that applies when compiling
2599    /// primitives on the action side (RHS) of this rule. Under
2600    /// seminaive, actions may write but not read; a `:naive` rule
2601    /// widens to [`Context::Full`] so writes and reads are both
2602    /// admissible.
2603    fn action_context(&self) -> crate::Context {
2604        if self.requires_read_context {
2605            crate::Context::Full
2606        } else {
2607            crate::Context::Write
2608        }
2609    }
2610
2611    fn entry(&mut self, x: &core::ResolvedAtomTerm) -> QueryEntry {
2612        self.entries
2613            .entry(x.clone())
2614            .or_insert_with(|| match x {
2615                core::GenericAtomTerm::Var(_, v) => self
2616                    .rb
2617                    .new_var_named(v.sort.column_ty(self.rb.egraph()), &v.name),
2618                core::GenericAtomTerm::Literal(_, l) => literal_to_entry(self.rb.egraph(), l),
2619                core::GenericAtomTerm::Global(..) => {
2620                    panic!("Globals should have been desugared")
2621                }
2622            })
2623            .clone()
2624    }
2625
2626    fn func(&self, f: &typechecking::FuncType) -> egglog_bridge::FunctionId {
2627        self.functions[&f.name].backend_id
2628    }
2629
2630    fn prim(
2631        &mut self,
2632        prim: &core::SpecializedPrimitive,
2633        args: &[core::ResolvedAtomTerm],
2634        ctx: crate::Context,
2635    ) -> (ExternalFunctionId, Vec<QueryEntry>, ColumnTy) {
2636        // The typechecker has already checked that this primitive is
2637        // valid in `ctx`; pick the runtime id that stamps the same ctx
2638        // onto the state wrapper when invoked.
2639        let resolved_id = prim.external_id(ctx);
2640
2641        let mut qe_args = self.args(args);
2642
2643        if prim.name() == "unstable-fn" {
2644            let core::ResolvedAtomTerm::Literal(_, Literal::String(ref name)) = args[0] else {
2645                panic!("expected string literal after `unstable-fn`")
2646            };
2647            // Pre-register a panic id used by `FunctionContainer::apply`
2648            // when the wrapped function is applied in a context that
2649            // doesn't admit it. Triggered at runtime via the egglog
2650            // panic side channel so misuse surfaces as an `Err` from
2651            // `run_rules` rather than a thread unwind.
2652            let panic_id = self.rb.new_panic(format!(
2653                "unstable-fn over `{name}` was applied in a context where its wrapped \
2654                 function is not valid for this call site, if in a rule, add :naive."
2655            ));
2656            let resolved = resolve_function_container_target_with_context(
2657                self.rb.egraph(),
2658                self.functions,
2659                self.type_info,
2660                name,
2661                prim,
2662                panic_id,
2663            )
2664            .unwrap_or_else(|err| panic!("{err}"));
2665
2666            qe_args[0] = self.rb.egraph().base_value_constant(resolved);
2667        }
2668
2669        (
2670            resolved_id,
2671            qe_args,
2672            prim.output().column_ty(self.rb.egraph()),
2673        )
2674    }
2675
2676    fn args<'b>(
2677        &mut self,
2678        args: impl IntoIterator<Item = &'b core::ResolvedAtomTerm>,
2679    ) -> Vec<QueryEntry> {
2680        args.into_iter().map(|x| self.entry(x)).collect()
2681    }
2682
2683    fn query(&mut self, query: &core::Query<ResolvedCall, ResolvedVar>, include_subsumed: bool) {
2684        for atom in &query.atoms {
2685            match &atom.head {
2686                ResolvedCall::Func(f) => {
2687                    let f = self.func(f);
2688                    let args = self.args(&atom.args);
2689                    let is_subsumed = match include_subsumed {
2690                        true => None,
2691                        false => Some(false),
2692                    };
2693                    self.rb.query_table(f, &args, is_subsumed).unwrap();
2694                }
2695                ResolvedCall::Primitive(p) => {
2696                    let ctx = self.query_context();
2697                    let (p, args, ty) = self.prim(p, &atom.args, ctx);
2698                    self.rb.query_prim(p, &args, ty).unwrap()
2699                }
2700            }
2701        }
2702    }
2703
2704    fn actions(&mut self, actions: &core::ResolvedCoreActions) -> Result<(), Error> {
2705        for action in &actions.0 {
2706            match action {
2707                core::GenericCoreAction::Let(span, v, f, args) => {
2708                    let v = core::GenericAtomTerm::Var(span.clone(), v.clone());
2709                    let y = match f {
2710                        ResolvedCall::Func(f) => {
2711                            let name = f.name.clone();
2712                            let f = self.func(f);
2713                            let args = self.args(args);
2714                            let span = span.clone();
2715                            self.rb.lookup(f, &args, move || {
2716                                format!("{span}: lookup of function {name} failed")
2717                            })
2718                        }
2719                        ResolvedCall::Primitive(p) => {
2720                            let name = p.name().to_owned();
2721                            let ctx = self.action_context();
2722                            let (p, args, ty) = self.prim(p, args, ctx);
2723                            let span = span.clone();
2724                            self.rb.call_external_func(p, &args, ty, move || {
2725                                format!("{span}: call of primitive {name} failed")
2726                            })
2727                        }
2728                    };
2729                    self.entries.insert(v, y.into());
2730                }
2731                core::GenericCoreAction::LetAtomTerm(span, v, x) => {
2732                    let v = core::GenericAtomTerm::Var(span.clone(), v.clone());
2733                    let x = self.entry(x);
2734                    self.entries.insert(v, x);
2735                }
2736                core::GenericCoreAction::Set(_, f, xs, y) => match f {
2737                    ResolvedCall::Primitive(..) => panic!("runtime primitive set!"),
2738                    ResolvedCall::Func(f) => {
2739                        let f = self.func(f);
2740                        let args = self.args(xs.iter().chain([y]));
2741                        self.rb.set(f, &args)
2742                    }
2743                },
2744                core::GenericCoreAction::Change(span, change, f, args) => match f {
2745                    ResolvedCall::Primitive(..) => panic!("runtime primitive change!"),
2746                    ResolvedCall::Func(f) => {
2747                        let name = f.name.clone();
2748                        let can_subsume = self.functions[&f.name].can_subsume;
2749                        let f = self.func(f);
2750                        let args = self.args(args);
2751                        match change {
2752                            Change::Delete => self.rb.remove(f, &args),
2753                            Change::Subsume if can_subsume => self.rb.subsume(f, &args),
2754                            Change::Subsume => {
2755                                return Err(Error::SubsumeMergeError(name, span.clone()));
2756                            }
2757                        }
2758                    }
2759                },
2760                core::GenericCoreAction::Union(_, x, y) => {
2761                    let x = self.entry(x);
2762                    let y = self.entry(y);
2763                    self.rb.union(x, y)
2764                }
2765                core::GenericCoreAction::Panic(_, message) => self.rb.panic(message.clone()),
2766            }
2767        }
2768        Ok(())
2769    }
2770
2771    fn build(self) -> egglog_bridge::RuleId {
2772        self.rb.build()
2773    }
2774}
2775
2776fn literal_to_entry(egraph: &egglog_bridge::EGraph, l: &Literal) -> QueryEntry {
2777    match l {
2778        Literal::Int(x) => egraph.base_value_constant::<i64>(*x),
2779        Literal::Float(x) => egraph.base_value_constant::<sort::F>(x.into()),
2780        Literal::String(x) => egraph.base_value_constant::<sort::S>(sort::S::new(x.clone())),
2781        Literal::Bool(x) => egraph.base_value_constant::<bool>(*x),
2782        Literal::Unit => egraph.base_value_constant::<()>(()),
2783    }
2784}
2785
2786fn literal_to_value(egraph: &egglog_bridge::EGraph, l: &Literal) -> Value {
2787    match l {
2788        Literal::Int(x) => egraph.base_values().get::<i64>(*x),
2789        Literal::Float(x) => egraph.base_values().get::<sort::F>(x.into()),
2790        Literal::String(x) => egraph.base_values().get::<sort::S>(sort::S::new(x.clone())),
2791        Literal::Bool(x) => egraph.base_values().get::<bool>(*x),
2792        Literal::Unit => egraph.base_values().get::<()>(()),
2793    }
2794}
2795
2796#[derive(Debug, Error)]
2797pub enum Error {
2798    #[error(transparent)]
2799    ParseError(#[from] ParseError),
2800    #[error(transparent)]
2801    NotFoundError(#[from] NotFoundError),
2802    #[error(transparent)]
2803    TypeError(#[from] TypeError),
2804    #[error(transparent)]
2805    ApiError(#[from] crate::api::ApiError),
2806    #[error("Errors:\n{}", ListDisplay(.0, "\n"))]
2807    TypeErrors(Vec<TypeError>),
2808    #[error("{}\nCheck failed: \n{}", .1, ListDisplay(.0, "\n"))]
2809    CheckError(Vec<Fact>, Span),
2810    #[error("{1}\nNo such ruleset: {0}")]
2811    NoSuchRuleset(String, Span),
2812    #[error(
2813        "{1}\nAttempted to add a rule to combined ruleset {0}. Combined rulesets may only depend on other rulesets."
2814    )]
2815    CombinedRulesetError(String, Span),
2816    #[error("{0}")]
2817    BackendError(String),
2818    #[error("{0}\nTried to pop too much")]
2819    Pop(Span),
2820    #[error("{0}\nCommand should have failed.")]
2821    ExpectFail(Span),
2822    #[error("{2}\nIO error: {0}: {1}")]
2823    IoError(PathBuf, std::io::Error, Span),
2824    #[error("{1}\nCannot subsume function with merge: {0}")]
2825    SubsumeMergeError(String, Span),
2826    #[error("extraction failure: {:?}", .0)]
2827    ExtractError(String),
2828    #[error("{span}\n{error}")]
2829    ProofError {
2830        span: Span,
2831        #[source]
2832        error: ProveExistsError,
2833    },
2834    #[error("{1}\n{2}\nShadowing is not allowed, but found {0}")]
2835    Shadowing(String, Span, Span),
2836    #[error("{1}\nCommand already exists: {0}")]
2837    CommandAlreadyExists(String, Span),
2838    #[error("Incorrect format in file '{0}'.")]
2839    InputFileFormatError(String),
2840    #[error(
2841        "Command is not supported by the current proof term encoding implementation.\n\
2842         Reason: {reason}\n\
2843         This typically means the command uses constructs that cannot yet be represented as proof terms.\n\
2844         Consider disabling proof term encoding for this run or rewriting the command to avoid unsupported features.\n\
2845         Offending command: {command}"
2846    )]
2847    UnsupportedProofCommand {
2848        command: String,
2849        reason: ProofEncodingUnsupportedReason,
2850    },
2851    #[error(
2852        "`{api}` is incompatible with proof mode: {reason} \
2853         Disable proofs or make the operation a command in the syntax of the egglog language and use `EGraph::parse_and_run`."
2854    )]
2855    ProofsIncompatibleApi {
2856        api: &'static str,
2857        reason: &'static str,
2858    },
2859}
2860
2861#[cfg(test)]
2862mod tests {
2863    use crate::constraint::SimpleTypeConstraint;
2864    use crate::*;
2865
2866    use crate::PureState;
2867
2868    #[derive(Clone)]
2869    struct InnerProduct {
2870        vec: ArcSort,
2871    }
2872
2873    // `InnerProduct` is pure, so it declares
2874    // `State = PureState` and is usable in all
2875    // contexts. The Rust type checker enforces that the body only uses
2876    // methods available on `PureState`.
2877    impl Primitive for InnerProduct {
2878        fn name(&self) -> &str {
2879            "inner-product"
2880        }
2881
2882        fn get_type_constraints(&self, span: &Span) -> Box<dyn crate::constraint::TypeConstraint> {
2883            SimpleTypeConstraint::new(
2884                self.name(),
2885                vec![self.vec.clone(), self.vec.clone(), I64Sort.to_arcsort()],
2886                span.clone(),
2887            )
2888            .into_box()
2889        }
2890    }
2891
2892    impl PurePrim for InnerProduct {
2893        fn apply<'a, 'db>(&self, state: PureState<'a, 'db>, args: &[Value]) -> Option<Value> {
2894            let mut sum = 0;
2895            let vec1 = state
2896                .container_values()
2897                .get_val::<VecContainer>(args[0])
2898                .unwrap();
2899            let vec2 = state
2900                .container_values()
2901                .get_val::<VecContainer>(args[1])
2902                .unwrap();
2903            assert_eq!(vec1.data.len(), vec2.data.len());
2904            for (a, b) in vec1.data.iter().zip(vec2.data.iter()) {
2905                let a = state.base_values().unwrap::<i64>(*a);
2906                let b = state.base_values().unwrap::<i64>(*b);
2907                sum += a * b;
2908            }
2909            Some(state.base_values().get::<i64>(sum))
2910        }
2911    }
2912
2913    #[derive(Clone)]
2914    struct FullOnly;
2915
2916    impl Primitive for FullOnly {
2917        fn name(&self) -> &str {
2918            "full-only"
2919        }
2920
2921        fn get_type_constraints(&self, span: &Span) -> Box<dyn crate::constraint::TypeConstraint> {
2922            SimpleTypeConstraint::new(self.name(), vec![I64Sort.to_arcsort()], span.clone())
2923                .into_box()
2924        }
2925    }
2926
2927    impl FullPrim for FullOnly {
2928        fn apply<'a, 'db>(&self, state: FullState<'a, 'db>, _args: &[Value]) -> Option<Value> {
2929            Some(state.base_values().get::<i64>(1))
2930        }
2931    }
2932
2933    #[test]
2934    fn test_user_defined_primitive() {
2935        let mut egraph = EGraph::default();
2936        egraph
2937            .parse_and_run_program(None, "(sort IntVec (Vec i64))")
2938            .unwrap();
2939
2940        let int_vec_sort = egraph.get_arcsort_by(|s| {
2941            s.value_type() == Some(std::any::TypeId::of::<VecContainer>())
2942                && s.inner_sorts()[0].name() == I64Sort.name()
2943        });
2944
2945        egraph.add_pure_primitive(InnerProduct { vec: int_vec_sort }, None);
2946
2947        egraph
2948            .parse_and_run_program(
2949                None,
2950                "
2951                (let a (vec-of 1 2 3 4 5 6))
2952                (let b (vec-of 6 5 4 3 2 1))
2953                (check (= (inner-product a b) 56))
2954            ",
2955            )
2956            .unwrap();
2957    }
2958
2959    #[test]
2960    fn proof_support_accepts_container_sort_declarations() {
2961        let mut egraph = EGraph::default();
2962        let resolved = egraph
2963            .resolve_program(None, "(datatype X (x))\n(sort XPair (Pair X i64))")
2964            .unwrap();
2965        assert!(program_supports_proofs(&resolved, &egraph.type_info));
2966
2967        let mut egraph = EGraph::default();
2968        let resolved = egraph
2969            .resolve_program(None, "(datatype X (x))\n(sort XFn (UnstableFn (X) X))")
2970            .unwrap();
2971        assert!(program_supports_proofs(&resolved, &egraph.type_info));
2972    }
2973
2974    #[test]
2975    fn proof_support_rejects_unstable_fn_primitives_without_validators() {
2976        let mut egraph = EGraph::default();
2977        let resolved = egraph
2978            .resolve_program(
2979                None,
2980                r#"
2981                (datatype X (x))
2982                (sort XFn (UnstableFn (X) X))
2983                (function id (X) X :merge old)
2984                (let f (unstable-fn "id"))
2985                "#,
2986            )
2987            .unwrap();
2988        assert!(!program_supports_proofs(&resolved, &egraph.type_info));
2989    }
2990
2991    #[test]
2992    fn proof_support_accepts_set_primitive_validators() {
2993        let mut egraph = EGraph::default();
2994        let resolved = egraph
2995            .resolve_program(
2996                None,
2997                r#"
2998                (sort ISet (Set i64))
2999                (function Shared () ISet :merge (set-intersect old new))
3000
3001                (check (= (set-insert (set-empty) 1) (set-of 1)))
3002                (check (= (set-remove (set-of 1 2) 2) (set-of 1)))
3003                (check (= (set-length (set-of 1 2)) 2))
3004                (check (set-contains (set-of 1 2) 1))
3005                (check (set-not-contains (set-of 1 2) 3))
3006                (check (= (set-union (set-of 1) (set-of 2)) (set-of 1 2)))
3007                (check (= (set-diff (set-of 1 2) (set-of 2)) (set-of 1)))
3008                (check (= (set-intersect (set-of 1 2) (set-of 2 3)) (set-of 2)))
3009                "#,
3010            )
3011            .unwrap();
3012
3013        assert!(program_supports_proofs(&resolved, &egraph.type_info));
3014    }
3015
3016    /// `set-get` indexes the runtime value order, which the proof checker
3017    /// cannot reproduce from terms, so it has no validator.
3018    #[test]
3019    fn proof_support_rejects_set_get() {
3020        let mut egraph = EGraph::default();
3021        let resolved = egraph
3022            .resolve_program(
3023                None,
3024                r#"
3025                (sort ISet (Set i64))
3026                (check (= (set-get (set-of 1 2) 0) 1))
3027                "#,
3028            )
3029            .unwrap();
3030
3031        assert!(!program_supports_proofs(&resolved, &egraph.type_info));
3032    }
3033
3034    #[test]
3035    fn test_typecheck_expr_with_bindings_and_output_rejects_mismatch() {
3036        let mut egraph = EGraph::default();
3037        let mut parser = crate::ast::Parser::default();
3038        let expr = parser.get_expr_from_string(None, "(+ 1 2)").unwrap();
3039
3040        let resolved = egraph
3041            .typecheck_expr_with_bindings_and_output(
3042                &expr,
3043                &[],
3044                I64Sort.to_arcsort(),
3045                Context::Pure,
3046            )
3047            .unwrap();
3048        assert_eq!(resolved.output_type().name(), I64Sort.name());
3049
3050        let err = egraph
3051            .typecheck_expr_with_bindings_and_output(
3052                &expr,
3053                &[],
3054                BoolSort.to_arcsort(),
3055                Context::Pure,
3056            )
3057            .unwrap_err();
3058        match err {
3059            TypeError::Mismatch {
3060                expected, actual, ..
3061            } => {
3062                assert_eq!(expected.name(), BoolSort.name());
3063                assert_eq!(actual.name(), I64Sort.name());
3064            }
3065            other => panic!("expected mismatch, got {other:?}"),
3066        }
3067
3068        let literal = parser.get_expr_from_string(None, "1").unwrap();
3069        let err = egraph
3070            .typecheck_expr_with_bindings_and_output(
3071                &literal,
3072                &[],
3073                BoolSort.to_arcsort(),
3074                Context::Pure,
3075            )
3076            .unwrap_err();
3077        match err {
3078            TypeError::Mismatch {
3079                expected, actual, ..
3080            } => {
3081                assert_eq!(expected.name(), BoolSort.name());
3082                assert_eq!(actual.name(), I64Sort.name());
3083            }
3084            other => panic!("expected literal mismatch, got {other:?}"),
3085        }
3086    }
3087
3088    #[test]
3089    fn test_typecheck_expr_with_bindings_and_output_uses_explicit_bindings() {
3090        let mut egraph = EGraph::default();
3091        let mut parser = crate::ast::Parser::default();
3092        let expr = parser.get_expr_from_string(None, "(+ x 2)").unwrap();
3093        let bindings = vec![("x".to_string(), span!(), I64Sort.to_arcsort())];
3094
3095        let resolved = egraph
3096            .typecheck_expr_with_bindings_and_output(
3097                &expr,
3098                &bindings,
3099                I64Sort.to_arcsort(),
3100                Context::Pure,
3101            )
3102            .unwrap();
3103
3104        assert_eq!(resolved.output_type().name(), I64Sort.name());
3105    }
3106
3107    #[test]
3108    fn test_typecheck_expr_with_bindings_and_output_uses_context() {
3109        let mut egraph = EGraph::default();
3110        egraph.add_full_primitive(FullOnly, None);
3111        let mut parser = crate::ast::Parser::default();
3112        let expr = parser.get_expr_from_string(None, "(full-only)").unwrap();
3113
3114        let resolved = egraph
3115            .typecheck_expr_with_bindings_and_output(
3116                &expr,
3117                &[],
3118                I64Sort.to_arcsort(),
3119                Context::Full,
3120            )
3121            .unwrap();
3122        assert_eq!(resolved.output_type().name(), I64Sort.name());
3123
3124        let err = egraph
3125            .typecheck_expr_with_bindings_and_output(
3126                &expr,
3127                &[],
3128                I64Sort.to_arcsort(),
3129                Context::Pure,
3130            )
3131            .unwrap_err();
3132        match err {
3133            TypeError::UnboundFunction(name, _) => assert_eq!(name, "full-only"),
3134            other => panic!("expected unbound function, got {other:?}"),
3135        }
3136    }
3137
3138    #[test]
3139    fn test_typecheck_expr_with_bindings_and_output_rejects_duplicate_bindings() {
3140        let mut egraph = EGraph::default();
3141        let mut parser = crate::ast::Parser::default();
3142        let expr = parser.get_expr_from_string(None, "x").unwrap();
3143        let bindings = vec![
3144            ("x".to_string(), span!(), I64Sort.to_arcsort()),
3145            ("x".to_string(), span!(), BoolSort.to_arcsort()),
3146        ];
3147
3148        let err = egraph
3149            .typecheck_expr_with_bindings_and_output(
3150                &expr,
3151                &bindings,
3152                I64Sort.to_arcsort(),
3153                Context::Pure,
3154            )
3155            .unwrap_err();
3156
3157        match err {
3158            TypeError::AlreadyDefined(name, _) => assert_eq!(name, "x"),
3159            other => panic!("expected duplicate binding, got {other:?}"),
3160        }
3161    }
3162
3163    #[test]
3164    fn test_typecheck_expr_with_bindings_and_output_rewrites_globals() {
3165        let mut egraph = EGraph::default();
3166        egraph.parse_and_run_program(None, "(let $x 1)").unwrap();
3167        let mut parser = crate::ast::Parser::default();
3168        let expr = parser.get_expr_from_string(None, "$x").unwrap();
3169
3170        let resolved = egraph
3171            .typecheck_expr_with_bindings_and_output(
3172                &expr,
3173                &[],
3174                I64Sort.to_arcsort(),
3175                Context::Read,
3176            )
3177            .unwrap();
3178
3179        match resolved {
3180            ResolvedExpr::Call(_, ResolvedCall::Func(func), children) => {
3181                assert_eq!(func.name, "$x");
3182                assert!(children.is_empty());
3183                assert_eq!(func.output.name(), I64Sort.name());
3184            }
3185            other => panic!("expected global function call rewrite, got {other:?}"),
3186        }
3187    }
3188
3189    // Test that an `EGraph` is `Send` & `Sync`
3190    #[test]
3191    fn test_egraph_send_sync() {
3192        fn is_send<T: Send>(_t: &T) -> bool {
3193            true
3194        }
3195        fn is_sync<T: Sync>(_t: &T) -> bool {
3196            true
3197        }
3198        let egraph = EGraph::default();
3199        assert!(is_send(&egraph) && is_sync(&egraph));
3200    }
3201
3202    #[test]
3203    fn test_extension_state_clones_and_restores_with_egraph() {
3204        let mut egraph = EGraph::default();
3205        assert_eq!(egraph.extension_state::<usize>(), None);
3206        assert_eq!(egraph.clone().extension_state::<usize>(), None);
3207
3208        *egraph.extension_state_or_default::<usize>() = 1;
3209
3210        let mut cloned = egraph.clone();
3211        assert_eq!(cloned.extension_state::<usize>(), Some(&1));
3212        *cloned.extension_state_or_default::<usize>() = 2;
3213        assert_eq!(egraph.extension_state::<usize>(), Some(&1));
3214
3215        egraph.push();
3216        *egraph.extension_state_or_default::<usize>() = 3;
3217        egraph.pop().unwrap();
3218
3219        assert_eq!(egraph.extension_state::<usize>(), Some(&1));
3220    }
3221
3222    fn get_function(egraph: &EGraph, name: &str) -> Function {
3223        egraph.functions.get(name).unwrap().clone()
3224    }
3225
3226    fn get_value(egraph: &EGraph, name: &str) -> Value {
3227        let mut out = None;
3228        let id = get_function(egraph, name).backend_id;
3229        egraph.backend.for_each(id, |row| out = Some(row.vals[0]));
3230        out.unwrap()
3231    }
3232
3233    #[test]
3234    fn test_subsumed_unextractable_rebuild_arg() {
3235        // Tests that a term stays unextractable even after a rebuild after a union would change the value of one of its args
3236        let mut egraph = EGraph::default();
3237
3238        egraph
3239            .parse_and_run_program(
3240                None,
3241                r#"
3242                (datatype Math)
3243                (constructor container (Math) Math)
3244                (constructor expensive () Math :cost 100)
3245                (constructor cheap () Math)
3246                (constructor cheap-1 () Math)
3247                ; we make the container cheap so that it will be extracted if possible, but then we mark it as subsumed
3248                ; so the (expensive) expr should be extracted instead
3249                (let res (container (cheap)))
3250                (union res (expensive))
3251                (cheap)
3252                (cheap-1)
3253                (subsume (container (cheap)))
3254                "#,
3255            ).unwrap();
3256        // At this point (cheap) and (cheap-1) should have different values, because they aren't unioned
3257        let orig_cheap_value = get_value(&egraph, "cheap");
3258        let orig_cheap_1_value = get_value(&egraph, "cheap-1");
3259        assert_ne!(orig_cheap_value, orig_cheap_1_value);
3260        // Then we can union them
3261        egraph
3262            .parse_and_run_program(
3263                None,
3264                r#"
3265                (union (cheap-1) (cheap))
3266                "#,
3267            )
3268            .unwrap();
3269        // And verify that their values are now the same and different from the original (cheap) value.
3270        let new_cheap_value = get_value(&egraph, "cheap");
3271        let new_cheap_1_value = get_value(&egraph, "cheap-1");
3272        assert_eq!(new_cheap_value, new_cheap_1_value);
3273        assert!(new_cheap_value != orig_cheap_value || new_cheap_1_value != orig_cheap_1_value);
3274        // Now verify that if we extract, it still respects the unextractable, even though it's a different values now
3275        let outputs = egraph
3276            .parse_and_run_program(
3277                None,
3278                r#"
3279                (extract res)
3280                "#,
3281            )
3282            .unwrap();
3283        assert_eq!(outputs[0].to_string(), "(expensive)\n");
3284    }
3285
3286    #[test]
3287    fn test_subsumed_unextractable_rebuild_self() {
3288        // Tests that a term stays unextractable even after a rebuild after a union change its output value.
3289        let mut egraph = EGraph::default();
3290
3291        egraph
3292            .parse_and_run_program(
3293                None,
3294                r#"
3295                (datatype Math)
3296                (constructor container (Math) Math)
3297                (constructor expensive () Math :cost 100)
3298                (constructor cheap () Math)
3299                (expensive)
3300                (let x (cheap))
3301                (subsume (cheap))
3302                "#,
3303            )
3304            .unwrap();
3305
3306        let orig_cheap_value = get_value(&egraph, "cheap");
3307        // Then we can union them
3308        egraph
3309            .parse_and_run_program(
3310                None,
3311                r#"
3312                (union (expensive) x)
3313                "#,
3314            )
3315            .unwrap();
3316        // And verify that the cheap value is now different
3317        let new_cheap_value = get_value(&egraph, "cheap");
3318        assert_ne!(new_cheap_value, orig_cheap_value);
3319
3320        // Now verify that if we extract, it still respects the subsumption, even though it's a different values now
3321        let res = egraph
3322            .parse_and_run_program(
3323                None,
3324                r#"
3325                (extract x)
3326                "#,
3327            )
3328            .unwrap();
3329        assert_eq!(res[0].to_string(), "(expensive)\n");
3330    }
3331
3332    #[test]
3333    fn test_run_undefined_ruleset_errors() {
3334        let mut egraph = EGraph::default();
3335        let err = egraph
3336            .parse_and_run_program(None, "(ruleset test)\n(run test2 1)")
3337            .unwrap_err();
3338        assert!(matches!(err, Error::NoSuchRuleset(name, _) if name == "test2"));
3339    }
3340}