1use std::hash::Hasher;
2
3use crate::Context;
4use crate::proofs::proof_container_rebuild::register_container_rebuild_from_spec;
5use crate::{
6 core::{CoreActionContext, CoreRule, GenericActionsExt, ResolvedCall},
7 *,
8};
9use ast::{
10 MappedExprExt, ResolvedAction, ResolvedExpr, ResolvedFact, ResolvedRule, ResolvedVar, Rule,
11 RuleEvalMode,
12};
13use core_relations::ExternalFunction;
14use egglog_ast::generic_ast::GenericAction;
15use egglog_bridge::ActionRegistry;
16use enum_map::EnumMap;
17use std::sync::{Arc, RwLock};
18
19#[derive(Clone)]
24struct PurePrimWrapper<T> {
25 prim: T,
26 ctx: Context,
31}
32
33impl<T: PurePrim + Clone> ExternalFunction for PurePrimWrapper<T> {
34 fn invoke(&self, exec_state: &mut ExecutionState, args: &[Value]) -> Option<Value> {
35 self.prim.apply(PureState::wrap(exec_state, self.ctx), args)
36 }
37}
38
39#[derive(Clone)]
44struct RegistryPrimWrapper<T, S> {
45 prim: T,
46 registry: Arc<RwLock<ActionRegistry>>,
47 ctx: Context,
49 _wrap: std::marker::PhantomData<fn() -> S>,
50}
51
52trait RegistryWrap<T>: Clone + Send + Sync {
53 fn invoke(
54 prim: &T,
55 exec_state: &mut ExecutionState,
56 ctx: Context,
57 args: &[Value],
58 registry: &ActionRegistry,
59 ) -> Option<Value>;
60}
61
62#[derive(Clone)]
63struct WrapRead;
64impl<T: ReadPrim> RegistryWrap<T> for WrapRead {
65 #[inline]
66 fn invoke(
67 prim: &T,
68 exec_state: &mut ExecutionState,
69 ctx: Context,
70 args: &[Value],
71 registry: &ActionRegistry,
72 ) -> Option<Value> {
73 prim.apply(ReadState::wrap(exec_state, registry, ctx), args)
74 }
75}
76#[derive(Clone)]
77struct WrapWrite;
78impl<T: WritePrim> RegistryWrap<T> for WrapWrite {
79 #[inline]
80 fn invoke(
81 prim: &T,
82 exec_state: &mut ExecutionState,
83 ctx: Context,
84 args: &[Value],
85 registry: &ActionRegistry,
86 ) -> Option<Value> {
87 prim.apply(WriteState::wrap(exec_state, registry, ctx), args)
88 }
89}
90#[derive(Clone)]
91struct WrapFull;
92impl<T: FullPrim> RegistryWrap<T> for WrapFull {
93 #[inline]
94 fn invoke(
95 prim: &T,
96 exec_state: &mut ExecutionState,
97 ctx: Context,
98 args: &[Value],
99 registry: &ActionRegistry,
100 ) -> Option<Value> {
101 prim.apply(FullState::wrap(exec_state, registry, ctx), args)
102 }
103}
104
105impl<T: Clone + Send + Sync + 'static, S: RegistryWrap<T> + 'static> ExternalFunction
106 for RegistryPrimWrapper<T, S>
107{
108 fn invoke(&self, exec_state: &mut ExecutionState, args: &[Value]) -> Option<Value> {
109 let registry = self.registry.read().unwrap();
110 S::invoke(&self.prim, exec_state, self.ctx, args, ®istry)
111 }
112}
113
114#[derive(Clone, Debug)]
115pub struct FuncType {
116 pub name: String,
117 pub subtype: FunctionSubtype,
118 pub input: Vec<ArcSort>,
119 pub output: ArcSort,
120}
121
122impl PartialEq for FuncType {
123 fn eq(&self, other: &Self) -> bool {
124 if self.name == other.name
125 && self.subtype == other.subtype
126 && self.output.name() == other.output.name()
127 {
128 if self.input.len() != other.input.len() {
129 return false;
130 }
131 for (a, b) in self.input.iter().zip(other.input.iter()) {
132 if a.name() != b.name() {
133 return false;
134 }
135 }
136 true
137 } else {
138 false
139 }
140 }
141}
142
143impl Eq for FuncType {}
144
145impl Hash for FuncType {
146 fn hash<H: Hasher>(&self, state: &mut H) {
147 self.name.hash(state);
148 self.subtype.hash(state);
149 self.output.name().hash(state);
150 for inp in &self.input {
151 inp.name().hash(state);
152 }
153 }
154}
155pub type PrimitiveValidator = Arc<dyn Fn(&mut TermDag, &[TermId]) -> Option<TermId> + Send + Sync>;
159
160#[derive(Clone)]
161pub struct PrimitiveWithId {
162 pub(crate) primitive: Arc<dyn Primitive>,
163 pub(crate) validator: Option<PrimitiveValidator>,
164 pub(crate) context_ids: EnumMap<Context, Option<ExternalFunctionId>>,
169}
170
171impl PrimitiveWithId {
172 pub fn accept(&self, tys: &[Arc<dyn Sort>], typeinfo: &TypeInfo) -> bool {
175 let mut constraints = vec![];
176 let lits: Vec<_> = (0..tys.len())
177 .map(|i| AtomTerm::Literal(Span::Panic, Literal::Int(i as i64)))
178 .collect();
179 for (lit, ty) in lits.iter().zip(tys.iter()) {
180 constraints.push(constraint::assign(lit.clone(), ty.clone()))
181 }
182 constraints.extend(
183 self.primitive
184 .get_type_constraints(&Span::Panic)
185 .get(&lits, typeinfo),
186 );
187 let problem = Problem {
188 constraints,
189 range: HashSet::default(),
190 };
191 problem.solve(|sort| sort.name()).is_ok()
192 }
193
194 pub fn is_valid_in_context(&self, context: Context) -> bool {
196 self.context_ids[context].is_some()
197 }
198}
199
200impl Debug for PrimitiveWithId {
201 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
202 write!(f, "Prim({})", self.primitive.name())
203 }
204}
205
206#[derive(Clone, Default)]
208pub struct TypeInfo {
209 mksorts: HashMap<String, MkSort>,
210 reserved_primitives: HashSet<&'static str>,
212 pub(crate) sorts: HashMap<String, Arc<dyn Sort>>,
213 primitives: HashMap<String, Vec<PrimitiveWithId>>,
214 func_types: HashMap<String, FuncType>,
215 pub(crate) global_sorts: HashMap<String, ArcSort>,
216 pub(crate) non_unionable_sorts: HashSet<String>,
218}
219
220impl EGraph {
223 pub fn add_sort<S: Sort + 'static>(&mut self, sort: S, span: Span) -> Result<(), TypeError> {
227 self.add_arcsort(Arc::new(sort), span)
228 }
229
230 pub fn declare_sort(
234 &mut self,
235 name: impl Into<String>,
236 presort_and_args: &Option<(String, Vec<Expr>)>,
237 span: Span,
238 ) -> Result<(), TypeError> {
239 let name = name.into();
240 if self.type_info.func_types.contains_key(&name) {
241 return Err(TypeError::FunctionAlreadyBound(name, span));
242 }
243
244 let sort = match presort_and_args {
245 None => Arc::new(EqSort { name }),
246 Some((presort, args)) => {
247 if let Some(mksort) = self.type_info.mksorts.get(presort) {
248 mksort(&mut self.type_info, name, args)?
249 } else {
250 return Err(TypeError::PresortNotFound(presort.clone(), span));
251 }
252 }
253 };
254
255 self.add_arcsort(sort, span)
256 }
257
258 pub fn add_arcsort(&mut self, sort: ArcSort, span: Span) -> Result<(), TypeError> {
260 sort.register_type(&mut self.backend);
261
262 let name = sort.name();
263 match self.type_info.sorts.entry(name.to_owned()) {
264 HEntry::Occupied(_) => Err(TypeError::SortAlreadyBound(name.to_owned(), span)),
265 HEntry::Vacant(e) => {
266 e.insert(sort.clone());
267 sort.register_primitives(self);
268 Ok(())
269 }
270 }
271 }
272
273 pub fn add_pure_primitive<T>(&mut self, x: T, validator: Option<PrimitiveValidator>)
282 where
283 T: PurePrim + Clone,
284 {
285 self.register_per_context(x, validator, PureState::valid_contexts(), |x, ctx| {
286 Box::new(PurePrimWrapper { prim: x, ctx })
287 });
288 }
289
290 pub fn add_write_primitive<T>(&mut self, x: T, validator: Option<PrimitiveValidator>)
293 where
294 T: WritePrim + Clone,
295 {
296 self.register_registry_primitive::<T, WrapWrite>(
297 x,
298 validator,
299 WriteState::valid_contexts(),
300 );
301 }
302
303 pub fn add_read_primitive<T>(&mut self, x: T, validator: Option<PrimitiveValidator>)
306 where
307 T: ReadPrim + Clone,
308 {
309 self.register_registry_primitive::<T, WrapRead>(x, validator, ReadState::valid_contexts());
310 }
311
312 pub fn add_full_primitive<T>(&mut self, x: T, validator: Option<PrimitiveValidator>)
315 where
316 T: FullPrim + Clone,
317 {
318 self.register_registry_primitive::<T, WrapFull>(x, validator, FullState::valid_contexts());
319 }
320
321 fn register_registry_primitive<T, S>(
322 &mut self,
323 x: T,
324 validator: Option<PrimitiveValidator>,
325 valid_ctxs: &[Context],
326 ) where
327 T: Primitive + Clone,
328 S: RegistryWrap<T> + 'static,
329 {
330 let registry = self.backend.action_registry().clone();
331 self.register_per_context(x, validator, valid_ctxs, move |x, ctx| {
332 Box::new(RegistryPrimWrapper::<T, S> {
333 prim: x,
334 registry: registry.clone(),
335 ctx,
336 _wrap: std::marker::PhantomData,
337 })
338 });
339 }
340
341 fn register_per_context<T, F>(
351 &mut self,
352 x: T,
353 validator: Option<PrimitiveValidator>,
354 valid_ctxs: &[Context],
355 mut build_wrapper: F,
356 ) where
357 T: Primitive + Clone,
358 F: FnMut(T, Context) -> Box<dyn ExternalFunction>,
359 {
360 let primitive: Arc<dyn Primitive> = Arc::new(x.clone());
361 let name = primitive.name().to_owned();
362 let context_ids = EnumMap::from_fn(|ctx| {
363 valid_ctxs.contains(&ctx).then(|| {
364 self.backend
365 .register_external_func(build_wrapper(x.clone(), ctx))
366 })
367 });
368 self.type_info
369 .primitives
370 .entry(name)
371 .or_default()
372 .push(PrimitiveWithId {
373 primitive,
374 validator,
375 context_ids,
376 });
377 }
378}
379
380impl EGraph {
381 pub(crate) fn typecheck_program(
382 &mut self,
383 program: &Vec<NCommand>,
384 ) -> Result<Vec<ResolvedNCommand>, TypeError> {
385 let mut result = vec![];
386 for command in program {
387 result.push(self.typecheck_command(command)?);
388 }
389 Ok(result)
390 }
391
392 fn typecheck_command(&mut self, command: &NCommand) -> Result<ResolvedNCommand, TypeError> {
393 let symbol_gen = &mut self.parser.symbol_gen;
394
395 let command: ResolvedNCommand = match command {
396 NCommand::Function(fdecl) => {
397 let resolved = self.type_info.typecheck_function(symbol_gen, fdecl)?;
398 if resolved.internal_let {
401 let output_sort = self.type_info.sorts.get(&fdecl.schema.output).unwrap();
402 self.type_info
403 .global_sorts
404 .insert(fdecl.name.clone(), output_sort.clone());
405 }
406 ResolvedNCommand::Function(resolved)
407 }
408 NCommand::NormRule { rule } => ResolvedNCommand::NormRule {
409 rule: self
410 .type_info
411 .typecheck_rule(symbol_gen, rule, self.seminaive)?,
412 },
413 NCommand::Sort {
414 span,
415 name,
416 presort_and_args,
417 uf,
418 proof_func,
419 container_rebuild,
420 proof_constructors,
421 unionable,
422 } => {
423 self.declare_sort(name.clone(), presort_and_args, span.clone())?;
426 if !unionable {
428 self.type_info.non_unionable_sorts.insert(name.clone());
429 }
430 if let Some((uf_ctor, uf_index)) = uf {
435 self.proof_state
436 .uf_parent
437 .insert(name.clone(), uf_ctor.clone());
438 if let Some(uf_index) = uf_index {
439 self.proof_state
440 .uf_function
441 .insert(name.clone(), uf_index.clone());
442 }
443 }
444 if let Some(pf) = proof_func {
445 self.proof_state
446 .proof_func_parent
447 .insert(name.clone(), pf.clone());
448 }
449 if let Some(pc) = proof_constructors {
453 let names = &mut self.proof_state.proof_names;
454 names.proof_datatype = name.clone();
455 names.congr_constructor = pc.congr.clone();
456 names.eq_trans_constructor = pc.trans.clone();
457 names.eq_sym_constructor = pc.sym.clone();
458 names.container_normalize_constructor = pc.normalize.clone();
459 }
460 if let Some(spec) = container_rebuild {
465 register_container_rebuild_from_spec(self, name, spec);
466 }
467 ResolvedNCommand::Sort {
468 span: span.clone(),
469 name: name.clone(),
470 presort_and_args: presort_and_args.clone(),
471 uf: uf.clone(),
472 proof_func: proof_func.clone(),
473 container_rebuild: container_rebuild.clone(),
474 proof_constructors: proof_constructors.clone(),
475 unionable: *unionable,
476 }
477 }
478 NCommand::CoreAction(action @ Action::Let(span, var, _)) => {
479 let action = self.type_info.typecheck_standalone_action(
480 symbol_gen,
481 action,
482 &Default::default(),
483 Context::Full,
484 )?;
485 self.ensure_global_name_prefix(span, var)?;
486 let ResolvedAction::Let(_, resolved_var, _) = &action else {
487 unreachable!("typechecking an Action::Let should return ResolvedAction::Let")
488 };
489 self.type_info
490 .global_sorts
491 .insert(resolved_var.name.clone(), resolved_var.sort.clone());
492 ResolvedNCommand::CoreAction(action)
493 }
494 NCommand::CoreAction(action) => {
495 ResolvedNCommand::CoreAction(self.type_info.typecheck_standalone_action(
496 symbol_gen,
497 action,
498 &Default::default(),
499 Context::Full,
500 )?)
501 }
502 NCommand::Extract(span, expr, variants) => {
503 let res_expr = self.type_info.typecheck_standalone_expr(
504 symbol_gen,
505 expr,
506 &Default::default(),
507 Context::Full,
508 )?;
509
510 let res_variants = self.type_info.typecheck_standalone_expr(
511 symbol_gen,
512 variants,
513 &Default::default(),
514 Context::Full,
515 )?;
516 if res_variants.output_type().name() != I64Sort.name() {
517 return Err(TypeError::Mismatch {
518 expr: variants.clone(),
519 expected: I64Sort.to_arcsort(),
520 actual: res_variants.output_type(),
521 });
522 }
523
524 ResolvedNCommand::Extract(span.clone(), res_expr, res_variants)
525 }
526 NCommand::Check(span, facts) => ResolvedNCommand::Check(
527 span.clone(),
528 self.type_info.typecheck_facts(symbol_gen, facts)?,
529 ),
530 NCommand::Fail(span, cmd) => {
531 ResolvedNCommand::Fail(span.clone(), Box::new(self.typecheck_command(cmd)?))
532 }
533 NCommand::RunSchedule(schedule) => ResolvedNCommand::RunSchedule(
534 self.type_info.typecheck_schedule(symbol_gen, schedule)?,
535 ),
536 NCommand::Pop(span, n) => ResolvedNCommand::Pop(span.clone(), *n),
537 NCommand::Push(n) => ResolvedNCommand::Push(*n),
538 NCommand::AddRuleset(span, ruleset) => {
539 ResolvedNCommand::AddRuleset(span.clone(), ruleset.clone())
540 }
541 NCommand::UnstableCombinedRuleset(span, name, sub_rulesets) => {
542 ResolvedNCommand::UnstableCombinedRuleset(
543 span.clone(),
544 name.clone(),
545 sub_rulesets.clone(),
546 )
547 }
548 NCommand::PrintOverallStatistics(span, file) => {
549 ResolvedNCommand::PrintOverallStatistics(span.clone(), file.clone())
550 }
551 NCommand::PrintFunction(span, table, size, file, mode) => {
552 ResolvedNCommand::PrintFunction(
553 span.clone(),
554 table.clone(),
555 *size,
556 file.clone(),
557 *mode,
558 )
559 }
560 NCommand::PrintSize(span, n) => {
561 ResolvedNCommand::PrintSize(span.clone(), n.clone())
563 }
564 NCommand::ProveExists(span, constructor) => {
565 let func_type = self
566 .type_info
567 .get_func_type(constructor)
568 .ok_or_else(|| TypeError::UnboundFunction(constructor.clone(), span.clone()))?;
569 if func_type.subtype != FunctionSubtype::Constructor {
570 return Err(TypeError::ProveExistsRequiresConstructor(
571 constructor.clone(),
572 span.clone(),
573 ));
574 }
575 ResolvedNCommand::ProveExists(span.clone(), ResolvedCall::Func(func_type.clone()))
576 }
577 NCommand::Output { span, file, exprs } => {
578 let exprs = exprs
579 .iter()
580 .map(|expr| {
581 self.type_info.typecheck_standalone_expr(
582 symbol_gen,
583 expr,
584 &Default::default(),
585 Context::Full,
586 )
587 })
588 .collect::<Result<Vec<_>, _>>()?;
589 ResolvedNCommand::Output {
590 span: span.clone(),
591 file: file.clone(),
592 exprs,
593 }
594 }
595 NCommand::Input { span, name, file } => ResolvedNCommand::Input {
596 span: span.clone(),
597 name: name.clone(),
598 file: file.clone(),
599 },
600 NCommand::UserDefined(span, name, exprs) => {
601 ResolvedNCommand::UserDefined(span.clone(), name.clone(), exprs.clone())
602 }
603 };
604 if let ResolvedNCommand::NormRule { rule } = &command {
605 self.warn_for_prefixed_non_globals_in_rule(rule)?;
606 }
607 Ok(command)
608 }
609
610 fn warn_for_prefixed_non_globals_in_var(
611 &mut self,
612 span: &Span,
613 var: &ResolvedVar,
614 ) -> Result<(), TypeError> {
615 if var.is_global_ref {
616 return Ok(());
617 }
618 if var.name.starts_with(crate::GLOBAL_NAME_PREFIX) {
619 self.warn_prefixed_non_globals(span, &var.name)?;
620 }
621 Ok(())
622 }
623
624 fn warn_for_prefixed_non_globals_in_rule(
625 &mut self,
626 rule: &ResolvedRule,
627 ) -> Result<(), TypeError> {
628 let mut res: Result<(), TypeError> = Ok(());
629
630 for fact in &rule.body {
631 fact.visit_vars(&mut |span, var| {
632 if res.is_ok() {
633 res = self.warn_for_prefixed_non_globals_in_var(span, var);
634 }
635 });
636 }
637
638 rule.head.visit_vars(&mut |span, var| {
639 if res.is_ok() {
640 res = self.warn_for_prefixed_non_globals_in_var(span, var);
641 }
642 });
643 res
644 }
645}
646
647impl TypeInfo {
648 pub fn add_presort<S: Presort>(&mut self, span: Span) -> Result<(), TypeError> {
650 let name = S::presort_name();
651 match self.mksorts.entry(name.to_owned()) {
652 HEntry::Occupied(_) => Err(TypeError::SortAlreadyBound(name.to_owned(), span)),
653 HEntry::Vacant(e) => {
654 e.insert(S::make_sort);
655 self.reserved_primitives.extend(S::reserved_primitives());
656 Ok(())
657 }
658 }
659 }
660
661 pub fn get_sorts_by<S: Sort>(&self, pred: impl Fn(&Arc<S>) -> bool) -> Vec<Arc<S>> {
663 let mut results = Vec::new();
664 for sort in self.sorts.values() {
665 let sort = sort.clone().as_arc_any();
666 if let Ok(sort) = Arc::downcast(sort)
667 && pred(&sort)
668 {
669 results.push(sort);
670 }
671 }
672 results
673 }
674
675 pub fn get_sorts<S: Sort>(&self) -> Vec<Arc<S>> {
677 self.get_sorts_by(|_| true)
678 }
679
680 pub fn get_sort_by<S: Sort>(&self, pred: impl Fn(&Arc<S>) -> bool) -> Arc<S> {
682 let results = self.get_sorts_by(pred);
683 assert_eq!(
684 results.len(),
685 1,
686 "Expected exactly one sort for type {}",
687 std::any::type_name::<S>()
688 );
689 results.into_iter().next().unwrap()
690 }
691
692 pub fn get_sort<S: Sort>(&self) -> Arc<S> {
694 self.get_sort_by(|_| true)
695 }
696
697 pub fn get_arcsorts_by(&self, f: impl Fn(&ArcSort) -> bool) -> Vec<ArcSort> {
699 self.sorts.values().filter(|&x| f(x)).cloned().collect()
700 }
701
702 pub fn get_arcsort_by(&self, f: impl Fn(&ArcSort) -> bool) -> ArcSort {
704 let results = self.get_arcsorts_by(f);
705 assert_eq!(
706 results.len(),
707 1,
708 "Expected exactly one sort matching the given predicate"
709 );
710 results.into_iter().next().unwrap()
711 }
712
713 pub fn get_arcsort_for_value_type<T: 'static>(&self) -> ArcSort {
715 let results = self.get_arcsorts_by(|s| s.value_type() == Some(std::any::TypeId::of::<T>()));
716 assert_eq!(
717 results.len(),
718 1,
719 "Expected exactly one sort for type `{}`",
720 std::any::type_name::<T>()
721 );
722 results.into_iter().next().unwrap()
723 }
724
725 pub fn is_sort_unionable(&self, sort: &ArcSort) -> bool {
729 sort.is_eq_sort() && !self.non_unionable_sorts.contains(sort.name())
730 }
731
732 fn function_to_functype(&self, func: &FunctionDecl) -> Result<FuncType, TypeError> {
733 let input = func
734 .schema
735 .input
736 .iter()
737 .map(|name| {
738 if let Some(sort) = self.sorts.get(name) {
739 Ok(sort.clone())
740 } else {
741 Err(TypeError::UndefinedSort(name.clone(), func.span.clone()))
742 }
743 })
744 .collect::<Result<Vec<_>, _>>()?;
745 let output = if let Some(sort) = self.sorts.get(&func.schema.output) {
746 Ok(sort.clone())
747 } else {
748 Err(TypeError::UndefinedSort(
749 func.schema.output.clone(),
750 func.span.clone(),
751 ))
752 }?;
753
754 Ok(FuncType {
755 name: func.name.clone(),
756 subtype: func.subtype,
757 input,
758 output: output.clone(),
759 })
760 }
761
762 fn typecheck_function(
763 &mut self,
764 symbol_gen: &mut SymbolGen,
765 fdecl: &FunctionDecl,
766 ) -> Result<ResolvedFunctionDecl, TypeError> {
767 if self.sorts.contains_key(&fdecl.name) {
768 return Err(TypeError::SortAlreadyBound(
769 fdecl.name.clone(),
770 fdecl.span.clone(),
771 ));
772 }
773 if self.is_primitive(&fdecl.name) {
774 return Err(TypeError::PrimitiveAlreadyBound(
775 fdecl.name.clone(),
776 fdecl.span.clone(),
777 ));
778 }
779 if fdecl.term_constructor.is_some() && fdecl.schema.input.is_empty() {
781 return Err(TypeError::TermConstructorNoInputs(
782 fdecl.name.clone(),
783 fdecl.span.clone(),
784 ));
785 }
786 let ftype = self.function_to_functype(fdecl)?;
787 if self.func_types.insert(fdecl.name.clone(), ftype).is_some() {
788 return Err(TypeError::FunctionAlreadyBound(
789 fdecl.name.clone(),
790 fdecl.span.clone(),
791 ));
792 }
793 let mut bound_vars = IndexMap::default();
794 let output_type = self.sorts.get(&fdecl.schema.output).unwrap();
795 if fdecl.subtype == FunctionSubtype::Constructor && !output_type.is_eq_sort() {
796 return Err(TypeError::ConstructorOutputNotSort(
797 fdecl.name.clone(),
798 fdecl.span.clone(),
799 ));
800 }
801 bound_vars.insert("old", (fdecl.span.clone(), output_type.clone()));
802 bound_vars.insert("new", (fdecl.span.clone(), output_type.clone()));
803
804 Ok(ResolvedFunctionDecl {
805 name: fdecl.name.clone(),
806 subtype: fdecl.subtype,
807 schema: fdecl.schema.clone(),
808 resolved_schema: ResolvedCall::Func(self.func_types.get(&fdecl.name).unwrap().clone()),
809 merge: match &fdecl.merge {
810 Some(merge) => Some(self.typecheck_standalone_expr(
814 symbol_gen,
815 merge,
816 &bound_vars,
817 Context::Write,
818 )?),
819 None => None,
820 },
821 cost: fdecl.cost,
822 unextractable: fdecl.unextractable,
823 internal_hidden: fdecl.internal_hidden,
824 internal_let: fdecl.internal_let,
825 span: fdecl.span.clone(),
826 term_constructor: fdecl.term_constructor.clone(),
827 })
828 }
829
830 fn typecheck_schedule(
831 &self,
832 symbol_gen: &mut SymbolGen,
833 schedule: &Schedule,
834 ) -> Result<ResolvedSchedule, TypeError> {
835 let schedule = match schedule {
836 Schedule::Repeat(span, times, schedule) => ResolvedSchedule::Repeat(
837 span.clone(),
838 *times,
839 Box::new(self.typecheck_schedule(symbol_gen, schedule)?),
840 ),
841 Schedule::Sequence(span, schedules) => {
842 let schedules = schedules
843 .iter()
844 .map(|schedule| self.typecheck_schedule(symbol_gen, schedule))
845 .collect::<Result<Vec<_>, _>>()?;
846 ResolvedSchedule::Sequence(span.clone(), schedules)
847 }
848 Schedule::Saturate(span, schedule) => ResolvedSchedule::Saturate(
849 span.clone(),
850 Box::new(self.typecheck_schedule(symbol_gen, schedule)?),
851 ),
852 Schedule::Run(span, RunConfig { ruleset, until }) => {
853 let until = until
854 .as_ref()
855 .map(|facts| self.typecheck_facts(symbol_gen, facts))
856 .transpose()?;
857 ResolvedSchedule::Run(
858 span.clone(),
859 ResolvedRunConfig {
860 ruleset: ruleset.clone(),
861 until,
862 },
863 )
864 }
865 };
866
867 Result::Ok(schedule)
868 }
869
870 fn typecheck_rule(
871 &self,
872 symbol_gen: &mut SymbolGen,
873 rule: &Rule,
874 global_seminaive: bool,
875 ) -> Result<ResolvedRule, TypeError> {
876 let Rule {
877 span,
878 head,
879 body,
880 name,
881 ruleset,
882 eval_mode,
883 no_decomp,
884 include_subsumed,
885 } = rule;
886 let mut constraints = vec![];
887
888 let read_contexts = !global_seminaive
892 || matches!(
893 eval_mode,
894 RuleEvalMode::Naive | RuleEvalMode::UnsafeSeminaive
895 );
896 let (query_ctx, action_ctx) = if read_contexts {
897 (Context::Read, Context::Full)
898 } else {
899 (Context::Pure, Context::Write)
900 };
901
902 let (query, mapped_query) = Facts(body.clone()).to_query(self, symbol_gen);
903 constraints.extend(query.get_constraints(self, query_ctx)?);
904
905 let mut binding = query.get_vars();
906 let mut ctx = CoreActionContext::new(self, &mut binding, symbol_gen, false);
909 let (actions, mapped_action) = head.to_core_actions(&mut ctx)?;
910
911 let mut problem = Problem::default();
912 problem.add_rule(
913 &CoreRule {
914 span: span.clone(),
915 body: query,
916 head: actions,
917 },
918 self,
919 symbol_gen,
920 query_ctx,
921 action_ctx,
922 )?;
923
924 let assignment = problem
925 .solve(|sort: &ArcSort| sort.name())
926 .map_err(|e| e.to_type_error())?;
927
928 let body: Vec<ResolvedFact> = assignment.annotate_facts(&mapped_query, self, query_ctx);
929 let actions: ResolvedActions =
930 assignment.annotate_actions(&mapped_action, self, action_ctx)?;
931
932 if !read_contexts {
935 self.check_no_function_lookups_in_actions(&actions)?;
936 }
937
938 Ok(ResolvedRule {
939 span: span.clone(),
940 body,
941 head: actions,
942 name: name.clone(),
943 ruleset: ruleset.clone(),
944 eval_mode: *eval_mode,
945 no_decomp: *no_decomp,
946 include_subsumed: *include_subsumed,
947 })
948 }
949
950 fn check_lookup_expr(&self, expr: &ResolvedExpr) -> Result<(), TypeError> {
951 if let Some(span) = self.expr_has_function_lookup(expr) {
952 return Err(TypeError::LookupInRuleDisallowed(
953 "function".to_string(),
954 span,
955 ));
956 }
957 Ok(())
958 }
959
960 fn check_no_function_lookups_in_actions(
961 &self,
962 actions: &ResolvedActions,
963 ) -> Result<(), TypeError> {
964 for action in actions.iter() {
965 match action {
966 GenericAction::Let(_, _, rhs) => self.check_lookup_expr(rhs)?,
967 GenericAction::Set(_, _, args, rhs) => {
968 for arg in args.iter() {
969 self.check_lookup_expr(arg)?;
970 }
971 self.check_lookup_expr(rhs)?;
972 }
973 GenericAction::Union(_, lhs, rhs) => {
974 self.check_lookup_expr(lhs)?;
975 self.check_lookup_expr(rhs)?;
976 }
977 GenericAction::Change(_, _, _, args) => {
978 for arg in args.iter() {
979 self.check_lookup_expr(arg)?;
980 }
981 }
982 GenericAction::Panic(..) => {}
983 GenericAction::Expr(_, expr) => self.check_lookup_expr(expr)?,
984 }
985 }
986 Ok(())
987 }
988
989 pub fn typecheck_facts(
990 &self,
991 symbol_gen: &mut SymbolGen,
992 facts: &[Fact],
993 ) -> Result<Vec<ResolvedFact>, TypeError> {
994 let (query, mapped_facts) = Facts(facts.to_vec()).to_query(self, symbol_gen);
995 let mut problem = Problem::default();
996 problem.add_query(&query, self, Context::Read)?;
999 let assignment = problem
1000 .solve(|sort: &ArcSort| sort.name())
1001 .map_err(|e| e.to_type_error())?;
1002 let annotated_facts = assignment.annotate_facts(&mapped_facts, self, Context::Read);
1003 Ok(annotated_facts)
1004 }
1005
1006 fn typecheck_standalone_actions(
1010 &self,
1011 symbol_gen: &mut SymbolGen,
1012 actions: &Actions,
1013 binding: &IndexMap<&str, (Span, ArcSort)>,
1014 context: Context,
1015 ) -> Result<ResolvedActions, TypeError> {
1016 let mut binding_set: IndexSet<String> =
1017 binding.keys().copied().map(str::to_string).collect();
1018 let mut ctx = CoreActionContext::new(self, &mut binding_set, symbol_gen, false);
1021 let (actions, mapped_action) = actions.to_core_actions(&mut ctx)?;
1022 let mut problem = Problem::default();
1023
1024 problem.add_actions(&actions, self, symbol_gen, context)?;
1025
1026 for (var, (span, sort)) in binding {
1028 problem.assign_local_var_type(var, span.clone(), sort.clone())?;
1029 }
1030
1031 let assignment = problem
1032 .solve(|sort: &ArcSort| sort.name())
1033 .map_err(|e| e.to_type_error())?;
1034
1035 let annotated_actions = assignment.annotate_actions(&mapped_action, self, context)?;
1036 Ok(annotated_actions)
1037 }
1038
1039 fn typecheck_standalone_expr(
1040 &self,
1041 symbol_gen: &mut SymbolGen,
1042 expr: &Expr,
1043 binding: &IndexMap<&str, (Span, ArcSort)>,
1044 context: Context,
1045 ) -> Result<ResolvedExpr, TypeError> {
1046 let action = Action::Expr(expr.span(), expr.clone());
1047 let typechecked_action =
1048 self.typecheck_standalone_action(symbol_gen, &action, binding, context)?;
1049 match typechecked_action {
1050 ResolvedAction::Expr(_, expr) => Ok(expr),
1051 _ => unreachable!(),
1052 }
1053 }
1054
1055 pub(crate) fn typecheck_expr_with_output(
1056 &self,
1057 symbol_gen: &mut SymbolGen,
1058 expr: &Expr,
1059 binding: &IndexMap<&str, (Span, ArcSort)>,
1060 output_sort: ArcSort,
1061 context: Context,
1062 ) -> Result<ResolvedExpr, TypeError> {
1063 let action = Action::Expr(expr.span(), expr.clone());
1064 let mut binding_set: IndexSet<String> =
1065 binding.keys().copied().map(str::to_string).collect();
1066 let mut ctx = CoreActionContext::new(self, &mut binding_set, symbol_gen, false);
1067 let (actions, mapped_action) = Actions::singleton(action).to_core_actions(&mut ctx)?;
1068 let mut problem = Problem::default();
1069
1070 problem.add_actions(&actions, self, symbol_gen, context)?;
1071
1072 for (var, (span, sort)) in binding {
1073 problem.assign_local_var_type(var, span.clone(), sort.clone())?;
1074 }
1075
1076 let [GenericAction::Expr(_, mapped_expr)] = mapped_action.0.as_slice() else {
1077 unreachable!("typechecking an expression should produce one expression action")
1078 };
1079 let output_atom = mapped_expr.get_corresponding_var_or_lit(self);
1080 problem.add_binding(output_atom, output_sort.clone());
1081
1082 let assignment = problem
1083 .solve(|sort: &ArcSort| sort.name())
1084 .map_err(|e| e.to_type_error())?;
1085
1086 let annotated_actions = assignment.annotate_actions(&mapped_action, self, context)?;
1087 match annotated_actions.0.into_iter().next().unwrap() {
1088 ResolvedAction::Expr(_, resolved_expr) => {
1089 let actual = resolved_expr.output_type();
1090 if actual.name() != output_sort.name() {
1091 return Err(TypeError::Mismatch {
1092 expr: expr.clone(),
1093 expected: output_sort,
1094 actual,
1095 });
1096 }
1097 Ok(resolved_expr)
1098 }
1099 _ => unreachable!(),
1100 }
1101 }
1102
1103 fn typecheck_standalone_action(
1104 &self,
1105 symbol_gen: &mut SymbolGen,
1106 action: &Action,
1107 binding: &IndexMap<&str, (Span, ArcSort)>,
1108 context: Context,
1109 ) -> Result<ResolvedAction, TypeError> {
1110 self.typecheck_standalone_actions(
1111 symbol_gen,
1112 &Actions::singleton(action.clone()),
1113 binding,
1114 context,
1115 )
1116 .map(|v| {
1117 assert_eq!(v.len(), 1);
1118 v.0.into_iter().next().unwrap()
1119 })
1120 }
1121
1122 pub fn get_sort_by_name(&self, sym: &str) -> Option<&ArcSort> {
1123 self.sorts.get(sym)
1124 }
1125
1126 pub fn get_prims(&self, sym: &str) -> Option<&[PrimitiveWithId]> {
1127 self.primitives.get(sym).map(Vec::as_slice)
1128 }
1129
1130 pub fn is_primitive(&self, sym: &str) -> bool {
1131 self.primitives.contains_key(sym) || self.reserved_primitives.contains(sym)
1132 }
1133
1134 pub fn primitive_has_validator(&self, id: ExternalFunctionId) -> bool {
1135 self.primitives
1136 .values()
1137 .flat_map(|v| v.iter())
1138 .any(|p| p.context_ids.iter().any(|(_, pid)| *pid == Some(id)) && p.validator.is_some())
1139 }
1140
1141 pub fn get_func_type(&self, sym: &str) -> Option<&FuncType> {
1142 self.func_types.get(sym)
1143 }
1144
1145 pub fn is_constructor(&self, sym: &str) -> bool {
1146 self.func_types
1147 .get(sym)
1148 .is_some_and(|f| f.subtype == FunctionSubtype::Constructor)
1149 }
1150
1151 pub fn get_global_sort(&self, sym: &str) -> Option<&ArcSort> {
1152 self.global_sorts.get(sym)
1153 }
1154
1155 pub fn is_global(&self, sym: &str) -> bool {
1156 self.global_sorts.contains_key(sym)
1157 }
1158
1159 pub fn expr_has_function_lookup(&self, expr: &ResolvedExpr) -> Option<Span> {
1163 use ast::GenericExpr;
1164
1165 expr.find(&mut |e| {
1166 if let GenericExpr::Call(span, ResolvedCall::Func(func_type), _) = e
1167 && func_type.subtype == FunctionSubtype::Custom
1168 && !self.is_global(&func_type.name)
1169 {
1170 return Some(span.clone());
1171 }
1172 None
1173 })
1174 }
1175}
1176
1177#[derive(Debug, Clone, Error)]
1178pub enum TypeError {
1179 #[error("{}\nArity mismatch, expected {expected} args: {expr}", .expr.span())]
1180 Arity { expr: Expr, expected: usize },
1181 #[error(
1182 "{}\n Expect expression {expr} to have type {}, but get type {}",
1183 .expr.span(), .expected.name(), .actual.name(),
1184 )]
1185 Mismatch {
1186 expr: Expr,
1187 expected: ArcSort,
1188 actual: ArcSort,
1189 },
1190 #[error("{1}\nUnbound symbol {0}")]
1191 Unbound(String, Span),
1192 #[error(
1193 "{1}\nVariable {0} is ungrounded. A variable is grounded when it appears as an argument to a constructor or function in the query, not just under primitives or equalities."
1194 )]
1195 Ungrounded(String, Span),
1196 #[error("{1}\nUndefined sort {0}")]
1197 UndefinedSort(String, Span),
1198 #[error("{1}\nUnbound function {0}")]
1199 UnboundFunction(String, Span),
1200 #[error("{1}\nprove-exists requires constructor function, but {0} is not a constructor")]
1201 ProveExistsRequiresConstructor(String, Span),
1202 #[error("{1}\nFunction already bound {0}")]
1203 FunctionAlreadyBound(String, Span),
1204 #[error("{1}\nSort {0} already declared.")]
1205 SortAlreadyBound(String, Span),
1206 #[error("{1}\nPrimitive {0} already declared.")]
1207 PrimitiveAlreadyBound(String, Span),
1208 #[error("Function type mismatch: expected {} => {}, actual {} => {}", .1.iter().map(|s| s.name().to_string()).collect::<Vec<_>>().join(", "), .0.name(), .3.iter().map(|s| s.name().to_string()).collect::<Vec<_>>().join(", "), .2.name())]
1209 FunctionTypeMismatch(ArcSort, Vec<ArcSort>, ArcSort, Vec<ArcSort>),
1210 #[error("{1}\nPresort {0} not found.")]
1211 PresortNotFound(String, Span),
1212 #[error("{}\nFailed to infer a type for: {}", .0.span(), .0)]
1213 InferenceFailure(Expr),
1214 #[error("{1}\nVariable {0} was already defined")]
1215 AlreadyDefined(String, Span),
1216 #[error("{1}\nThe output type of constructor function {0} must be sort")]
1217 ConstructorOutputNotSort(String, Span),
1218 #[error("{1}\nValue lookup of non-constructor function {0} in rule is disallowed.")]
1219 LookupInRuleDisallowed(String, Span),
1220 #[error("{1}\nCannot set constructor {0}. Use `union` instead or declare {0} as a function.")]
1221 SetConstructorDisallowed(String, Span),
1222 #[error("All alternative definitions considered failed\n{}", .0.iter().map(|e| format!(" {e}\n")).collect::<Vec<_>>().join(""))]
1223 AllAlternativeFailed(Vec<TypeError>),
1224 #[error("{}\nCannot union values of sort {}", .1, .0.name())]
1225 NonEqsortUnion(ArcSort, Span),
1226 #[error("{}\nCannot union values of sort {} because it is marked as non-unionable (e.g. from a relation)", .1, .0.name())]
1227 NonUnionableSort(ArcSort, Span),
1228 #[error(
1229 "{1}\nView table {0} with :internal-term-constructor must have at least one input (the e-class)."
1230 )]
1231 TermConstructorNoInputs(String, Span),
1232 #[error(
1233 "{span}\nNon-global variable `{name}` must not start with `{}`.",
1234 crate::GLOBAL_NAME_PREFIX
1235 )]
1236 NonGlobalPrefixed { name: String, span: Span },
1237 #[error(
1238 "{span}\nGlobal `{name}` must start with `{}`.",
1239 crate::GLOBAL_NAME_PREFIX
1240 )]
1241 GlobalMissingPrefix { name: String, span: Span },
1242}
1243
1244#[cfg(test)]
1245mod test {
1246 use crate::{EGraph, Error, typechecking::TypeError};
1247
1248 #[test]
1249 fn test_arity_mismatch() {
1250 let mut egraph = EGraph::default();
1251
1252 let prog = "
1253 (relation f (i64 i64))
1254 (rule ((f a b c)) ())
1255 ";
1256 let res = egraph.parse_and_run_program(None, prog);
1257 match res {
1258 Err(Error::TypeError(TypeError::Arity {
1259 expected: 2,
1260 expr: e,
1261 })) => {
1262 assert_eq!(e.span().string(), "(f a b c)");
1263 }
1264 _ => panic!("Expected arity mismatch, got: {res:?}"),
1265 }
1266 }
1267}