1use anyhow::{Result, anyhow};
27use mangle_ir::{Inst, InstId, Ir, NameId};
28use rustc_hash::{FxHashMap, FxHashSet};
29
30use crate::name_trie::NameTrie;
31use crate::type_expr::{self, TypeContext};
32
33pub struct BoundsChecker<'a> {
35 ir: &'a mut Ir,
36 name_trie: NameTrie,
37 rel_type_map: FxHashMap<NameId, Vec<Vec<InstId>>>,
40 rules_map: FxHashMap<NameId, Vec<(InstId, Vec<InstId>, Vec<InstId>)>>,
42 inferred: FxHashMap<NameId, Vec<Vec<InstId>>>,
44 visiting: FxHashSet<NameId>,
46 fresh_var_counter: usize,
48}
49
50impl<'a> BoundsChecker<'a> {
51 pub fn new(ir: &'a mut Ir) -> Self {
52 Self {
53 ir,
54 name_trie: NameTrie::new(),
55 rel_type_map: FxHashMap::default(),
56 rules_map: FxHashMap::default(),
57 inferred: FxHashMap::default(),
58 visiting: FxHashSet::default(),
59 fresh_var_counter: 0,
60 }
61 }
62
63 pub fn check(&mut self) -> Result<()> {
65 self.collect_declarations()?;
66 self.build_rules_map();
67 self.check_arity_consistency()?;
68 self.check_all_clauses()
69 }
70
71 fn fresh_var(&mut self) -> NameId {
73 let name = format!("?X{}", self.fresh_var_counter);
74 self.fresh_var_counter += 1;
75 self.ir.intern_name(&name)
76 }
77
78 fn collect_declarations(&mut self) -> Result<()> {
80 let insts: Vec<Inst> = self.ir.insts.clone();
81 for inst in &insts {
82 if let Inst::Decl { atom, bounds, .. } = inst {
83 let pred_name = self.atom_predicate(*atom);
84 if let Some(pred) = pred_name {
85 let mut alternatives = Vec::new();
86 for bound_id in bounds {
87 if let Inst::BoundDecl { base_terms } = self.ir.get(*bound_id) {
88 let base_terms = base_terms.clone();
89 for term in &base_terms {
91 self.name_trie.collect(self.ir, *term);
92 }
93 let any = type_expr::find_or_create_name(self.ir, "/any");
95 let mut ctx = TypeContext::default();
96 for term in &base_terms {
97 let mut vars = FxHashSet::default();
98 type_expr::collect_vars(self.ir, *term, &mut vars);
99 for v in vars {
100 ctx.entry(v).or_insert(any);
101 }
102 }
103 for term in &base_terms {
105 type_expr::wellformed_type(self.ir, &ctx, *term)?;
106 }
107 alternatives.push(base_terms);
108 }
109 }
110 if !alternatives.is_empty() {
111 self.rel_type_map.insert(pred, alternatives);
112 }
113 }
114 }
115 }
116 Ok(())
117 }
118
119 fn build_rules_map(&mut self) {
121 let insts: Vec<Inst> = self.ir.insts.clone();
122 for inst in &insts {
123 if let Inst::Rule {
124 head,
125 premises,
126 transform,
127 } = inst
128 {
129 if (!premises.is_empty() || !transform.is_empty())
131 && let Some(pred) = self.atom_predicate(*head)
132 {
133 self.rules_map.entry(pred).or_default().push((
134 *head,
135 premises.clone(),
136 transform.clone(),
137 ));
138 }
139 }
140 }
141 }
142
143 fn check_arity_consistency(&self) -> Result<()> {
148 let mut arity_map: FxHashMap<NameId, (usize, String)> = FxHashMap::default();
150 let mut errors: Vec<String> = Vec::new();
151
152 let insts: Vec<Inst> = self.ir.insts.clone();
153 for inst in &insts {
154 if let Inst::Rule {
155 head,
156 premises,
157 transform,
158 } = inst
159 {
160 let is_fact = premises.is_empty() && transform.is_empty();
161 if let Some(pred) = self.atom_predicate(*head) {
162 let expected_args = if self.ir.temporal_predicates.contains(&pred) {
163 let args = self.atom_args(*head);
164 if args.len() >= 2 {
165 args.len() - 2
166 } else {
167 args.len()
168 }
169 } else {
170 self.atom_args(*head).len()
171 };
172
173 let kind = if is_fact { "fact" } else { "rule" };
174 let pred_name = self.ir.resolve_name(pred).to_string();
175 let location = format!("{} {}({} arg(s))", kind, pred_name, expected_args);
176
177 if let Some(&(first_arity, ref first_location)) = arity_map.get(&pred) {
178 if first_arity != expected_args {
179 errors.push(format!(
180 "predicate '{}' used with inconsistent arity: {} vs {}",
181 pred_name, first_location, location
182 ));
183 }
184 } else {
185 arity_map.insert(pred, (expected_args, location));
186 }
187 }
188 }
189 }
190
191 if errors.is_empty() {
192 Ok(())
193 } else {
194 Err(anyhow!("arity error: {}", errors.join("; ")))
195 }
196 }
197
198 fn check_all_clauses(&mut self) -> Result<()> {
200 let insts: Vec<Inst> = self.ir.insts.clone();
201 for inst in &insts {
202 if let Inst::Rule {
203 head,
204 premises,
205 transform,
206 } = inst
207 {
208 let head = *head;
209 let premises = premises.clone();
210 let transform = transform.clone();
211 if let Some(pred) = self.atom_predicate(head)
212 && let Some(alternatives) = self.rel_type_map.get(&pred).cloned()
213 {
214 if premises.is_empty() && transform.is_empty() {
215 self.check_fact(head, &alternatives)?;
216 } else {
217 self.check_rule(head, &premises, &transform, &alternatives)?;
218 }
219 }
220 }
221 }
222 Ok(())
223 }
224
225 fn check_fact(&self, head: InstId, alternatives: &[Vec<InstId>]) -> Result<()> {
227 let args = self.atom_args(head);
228 let pred = self.atom_predicate(head).unwrap();
229 if args.is_empty() && alternatives.is_empty() {
230 return Ok(());
231 }
232
233 let mut errors = Vec::new();
234 for alt in alternatives {
235 match self.check_fact_against_bound(pred, &args, alt) {
236 Ok(()) => return Ok(()),
237 Err(e) => errors.push(e.to_string()),
238 }
239 }
240
241 if errors.is_empty() {
242 return Ok(());
243 }
244
245 let pred_name = self
246 .atom_predicate(head)
247 .map(|p| self.ir.resolve_name(p).to_string())
248 .unwrap_or_else(|| "?".to_string());
249 Err(anyhow!(
250 "fact {}(...) matches none of the bound decls: {}",
251 pred_name,
252 errors.join("; ")
253 ))
254 }
255
256 fn check_fact_against_bound(
258 &self,
259 pred: NameId,
260 args: &[InstId],
261 bound: &[InstId],
262 ) -> Result<()> {
263 let is_temporal = self.ir.temporal_predicates.contains(&pred);
264 let expected_args = if is_temporal {
265 bound.len() + 2
266 } else {
267 bound.len()
268 };
269 if args.len() != expected_args {
270 return Err(anyhow!(
271 "arity mismatch: fact has {} args, bound has {}{}",
272 args.len(),
273 bound.len(),
274 if is_temporal { " (+2 temporal)" } else { "" }
275 ));
276 }
277 for (i, (arg, type_expr)) in args.iter().zip(bound.iter()).enumerate() {
278 if !type_expr::has_type(self.ir, *type_expr, *arg) {
279 let arg_desc = self.describe_inst(*arg);
280 let type_desc = self.describe_inst(*type_expr);
281 return Err(anyhow!(
282 "argument {} ({}) does not have type {}",
283 i,
284 arg_desc,
285 type_desc
286 ));
287 }
288 }
289 Ok(())
290 }
291
292 fn check_rule(
297 &mut self,
298 head: InstId,
299 premises: &[InstId],
300 transforms: &[InstId],
301 alternatives: &[Vec<InstId>],
302 ) -> Result<()> {
303 let head_args = self.atom_args(head);
304 let pred = self.atom_predicate(head).unwrap();
305 let is_temporal = self.ir.temporal_predicates.contains(&pred);
306
307 let mut state = InferState::new();
309 for premise_id in premises {
310 state = self.infer_from_premise(*premise_id, state)?;
311 }
312
313 for transform_id in transforms {
315 if let Inst::Transform { var, app } = self.ir.get(*transform_id) {
316 let var = *var;
317 let app = *app;
318 if let Some(v) = var {
319 let tpe = self.bound_of_arg(app, &state.as_map());
320 state.add_or_refine_with_ir(self.ir, v, tpe);
321 }
322 }
323 }
324
325 let var_ranges = state.as_map();
327 let inferred: Vec<InstId> = head_args
328 .iter()
329 .map(|arg| self.bound_of_arg(*arg, &var_ranges))
330 .collect();
331
332 let check_len = if is_temporal && inferred.len() >= 2 {
334 inferred.len() - 2
335 } else {
336 inferred.len()
337 };
338 let inferred_trimmed = &inferred[..check_len];
339
340 let mut errors = Vec::new();
342 for alt in alternatives {
343 if alt.len() != inferred_trimmed.len() {
344 errors.push(format!(
345 "arity mismatch: head has {} args, bound has {}",
346 inferred_trimmed.len(),
347 alt.len()
348 ));
349 continue;
350 }
351 let any = type_expr::find_or_create_name(self.ir, "/any");
353 let mut ctx = TypeContext::default();
354 for t in alt.iter() {
355 let mut vars = FxHashSet::default();
356 type_expr::collect_vars(self.ir, *t, &mut vars);
357 for v in vars {
358 ctx.entry(v).or_insert(any);
359 }
360 }
361 let all_conform = inferred_trimmed
362 .iter()
363 .zip(alt.iter())
364 .all(|(inf, decl)| type_expr::set_conforms(self.ir, &ctx, *inf, *decl));
365 if all_conform {
366 return Ok(());
367 }
368 errors.push(format!(
369 "inferred [{}] does not conform to declared [{}]",
370 inferred_trimmed
371 .iter()
372 .map(|i| self.describe_inst(*i))
373 .collect::<Vec<_>>()
374 .join(", "),
375 alt.iter()
376 .map(|i| self.describe_inst(*i))
377 .collect::<Vec<_>>()
378 .join(", "),
379 ));
380 }
381
382 if errors.is_empty() {
383 return Ok(());
384 }
385
386 let pred_name = self
387 .atom_predicate(head)
388 .map(|p| self.ir.resolve_name(p).to_string())
389 .unwrap_or_else(|| "?".to_string());
390 Err(anyhow!(
391 "rule for {}(...) does not conform to declared bounds: {}",
392 pred_name,
393 errors.join("; ")
394 ))
395 }
396
397 fn infer_from_premise(
399 &mut self,
400 premise_id: InstId,
401 mut state: InferState,
402 ) -> Result<InferState> {
403 match self.ir.get(premise_id) {
404 Inst::Atom { predicate, args } => {
405 let pred = *predicate;
406 let args = args.clone();
407
408 let pred_name = self.ir.resolve_name(pred).to_string();
410 if pred_name == ":match_prefix" {
411 return self.infer_match_prefix(&args, state);
412 }
413 if pred_name == ":match_field" {
414 return self.infer_match_field(&args, state);
415 }
416 if pred_name == ":match_entry" {
417 return self.infer_match_entry(&args, state);
418 }
419 if pred_name == ":list:member" {
420 return self.infer_list_member(&args, state);
421 }
422
423 let var_ranges = state.as_map();
425 let feasible = self.get_or_infer_alternatives(pred, &args, &var_ranges);
426
427 if !feasible.is_empty() {
428 let first = &feasible[0].clone();
430 for (arg, type_id) in args.iter().zip(first.iter()) {
431 if let Inst::Var(v) = self.ir.get(*arg) {
432 let v = *v;
433 state.add_or_refine_with_ir(self.ir, v, *type_id);
434 }
435 }
436 } else if let Some(alternatives) = self.rel_type_map.get(&pred).cloned() {
437 if let Some(first_alt) = alternatives.first() {
439 for (arg, type_id) in args.iter().zip(first_alt.iter()) {
440 if let Inst::Var(v) = self.ir.get(*arg) {
441 let v = *v;
442 state.add_or_refine_with_ir(self.ir, v, *type_id);
443 }
444 }
445 }
446 }
447 Ok(state)
448 }
449 Inst::NegAtom(inner) => {
450 let inner = *inner;
451 if let Inst::Atom { predicate, args } = self.ir.get(inner) {
454 let pred = *predicate;
455 let args = args.clone();
456 let pred_name = self.ir.resolve_name(pred).to_string();
457
458 if pred_name == ":match_prefix" && args.len() >= 2 {
459 if let Inst::Var(v) = self.ir.get(args[0]) {
461 let v = *v;
462 let bound = self.bound_of_arg(args[1], &state.as_map());
463 if let Some(existing) = state.as_map().get(&v).copied()
464 && type_expr::is_union_type(self.ir, existing)
465 {
466 let refined =
467 type_expr::remove_from_union_type(self.ir, bound, existing);
468 if !type_expr::is_empty_type(self.ir, refined) {
469 state.set_var(v, refined);
470 }
471 }
472 }
473 }
474 }
476 Ok(state)
477 }
478 Inst::Eq(left, right) => {
479 let left = *left;
480 let right = *right;
481 let var_ranges = state.as_map();
482
483 if let Inst::Var(lv) = self.ir.get(left) {
484 let lv = *lv;
485 let tpe = self.bound_of_arg(right, &var_ranges);
486 state.add_or_refine_with_ir(self.ir, lv, tpe);
487 }
488 if let Inst::Var(rv) = self.ir.get(right) {
489 let rv = *rv;
490 let tpe = self.bound_of_arg(left, &state.as_map());
491 state.add_or_refine_with_ir(self.ir, rv, tpe);
492 }
493 Ok(state)
494 }
495 Inst::Ineq(left, right) => {
496 let left = *left;
497 let right = *right;
498 let var_ranges = state.as_map();
499
500 let left_tpe = self.bound_of_arg(left, &var_ranges);
502 let right_tpe = self.bound_of_arg(right, &var_ranges);
503 let ctx = TypeContext::default();
504 let meet = type_expr::lower_bound(self.ir, &ctx, &[left_tpe, right_tpe]);
505 if !type_expr::is_empty_type(self.ir, meet) {
506 if let Inst::Var(lv) = self.ir.get(left) {
507 let lv = *lv;
508 state.add_or_refine_with_ir(self.ir, lv, meet);
509 }
510 if let Inst::Var(rv) = self.ir.get(right) {
511 let rv = *rv;
512 state.add_or_refine_with_ir(self.ir, rv, meet);
513 }
514 }
515 Ok(state)
516 }
517 _ => Ok(state),
518 }
519 }
520
521 fn feasible_alternatives(
529 &mut self,
530 alternatives: &[Vec<InstId>],
531 args: &[InstId],
532 var_ranges: &FxHashMap<NameId, InstId>,
533 ) -> Vec<Vec<InstId>> {
534 let mut feasible = Vec::new();
535
536 for alt in alternatives {
537 if alt.len() != args.len() {
538 continue;
539 }
540
541 let mut arg_bound = Vec::new();
545 for (i, arg) in args.iter().enumerate() {
546 if let Inst::Var(v) = self.ir.get(*arg) {
547 let v = *v;
548 if let Some(&range) = var_ranges.get(&v) {
549 arg_bound.push(range);
550 } else {
551 arg_bound.push(alt[i]);
553 }
554 } else {
555 arg_bound.push(self.bound_of_arg(*arg, var_ranges));
556 }
557 }
558
559 let mut type_vars = FxHashSet::default();
561 for t in alt {
562 type_expr::collect_vars(self.ir, *t, &mut type_vars);
563 }
564
565 let mut subst: FxHashMap<NameId, InstId> = FxHashMap::default();
567 if !type_vars.is_empty() {
568 for v in &type_vars {
569 let fresh = self.fresh_var();
570 let fresh_id = self.ir.add_inst(Inst::Var(fresh));
571 subst.insert(*v, fresh_id);
572 }
573 }
574
575 let arg_bound_subst: Vec<InstId> = arg_bound
577 .iter()
578 .map(|t| type_expr::apply_subst(self.ir, *t, &subst))
579 .collect();
580 let alt_subst: Vec<InstId> = alt
581 .iter()
582 .map(|t| type_expr::apply_subst(self.ir, *t, &subst))
583 .collect();
584
585 let any = type_expr::find_or_create_name(self.ir, "/any");
587 let mut ctx = TypeContext::default();
588 for fresh_id in subst.values() {
589 if let Inst::Var(v) = self.ir.get(*fresh_id) {
590 ctx.insert(*v, any);
591 }
592 }
593
594 let mut is_feasible = true;
596 let mut result_types = Vec::new();
597 for (ab, at) in arg_bound_subst.iter().zip(alt_subst.iter()) {
598 let meet = type_expr::lower_bound(self.ir, &ctx, &[*ab, *at]);
599 if type_expr::is_empty_type(self.ir, meet) {
600 is_feasible = false;
601 break;
602 }
603 result_types.push(meet);
604 }
605
606 if is_feasible {
607 feasible.push(result_types);
608 }
609 }
610 feasible
611 }
612
613 fn get_or_infer_alternatives(
618 &mut self,
619 pred: NameId,
620 args: &[InstId],
621 var_ranges: &FxHashMap<NameId, InstId>,
622 ) -> Vec<Vec<InstId>> {
623 if let Some(alts) = self.rel_type_map.get(&pred).cloned() {
625 return self.feasible_alternatives(&alts, args, var_ranges);
626 }
627
628 if let Some(alts) = self.inferred.get(&pred).cloned() {
630 return self.feasible_alternatives(&alts, args, var_ranges);
631 }
632
633 if self.visiting.contains(&pred) {
636 let any = type_expr::find_or_create_name(self.ir, "/any");
637 return vec![vec![any; args.len()]];
638 }
639
640 self.visiting.insert(pred);
642 let inferred = self.infer_rel_types(pred);
643 self.visiting.remove(&pred);
644
645 if !inferred.is_empty() {
646 self.inferred.insert(pred, inferred.clone());
647 return self.feasible_alternatives(&inferred, args, var_ranges);
648 }
649
650 Vec::new()
651 }
652
653 fn infer_rel_types(&mut self, pred: NameId) -> Vec<Vec<InstId>> {
658 let rules = match self.rules_map.get(&pred) {
659 Some(r) => r.clone(),
660 None => return Vec::new(),
661 };
662
663 let mut alternatives: Vec<Vec<InstId>> = Vec::new();
664
665 for (head, premises, transforms) in &rules {
666 if let Some(inferred) = self.infer_clause(*head, premises, transforms) {
668 alternatives.push(inferred);
669 }
670 }
671
672 alternatives
673 }
674
675 fn infer_clause(
677 &mut self,
678 head: InstId,
679 premises: &[InstId],
680 transforms: &[InstId],
681 ) -> Option<Vec<InstId>> {
682 let head_args = self.atom_args(head);
683 let mut state = InferState::new();
684
685 for premise_id in premises {
686 match self.infer_from_premise(*premise_id, state) {
687 Ok(new_state) => state = new_state,
688 Err(_) => return None,
689 }
690 }
691
692 for transform_id in transforms {
694 if let Inst::Transform { var, app } = self.ir.get(*transform_id) {
695 let var = *var;
696 let app = *app;
697 if let Some(v) = var {
698 let tpe = self.bound_of_arg(app, &state.as_map());
699 state.add_or_refine_with_ir(self.ir, v, tpe);
700 }
701 }
702 }
703
704 let var_ranges = state.as_map();
706 let inferred: Vec<InstId> = head_args
707 .iter()
708 .map(|arg| self.bound_of_arg(*arg, &var_ranges))
709 .collect();
710
711 Some(inferred)
712 }
713
714 fn infer_match_prefix(&mut self, args: &[InstId], mut state: InferState) -> Result<InferState> {
716 if args.len() != 2 {
717 return Ok(state);
718 }
719 let var_ranges = state.as_map();
720 let tpe = self.bound_of_arg(args[0], &var_ranges);
721 let prefix = self.bound_of_arg(args[1], &var_ranges);
722
723 let ctx = TypeContext::default();
724 let meet = type_expr::lower_bound(self.ir, &ctx, &[tpe, prefix]);
725 if !type_expr::is_empty_type(self.ir, meet) {
726 if let Inst::Var(v) = self.ir.get(args[0]) {
727 let v = *v;
728 state.add_or_refine_with_ir(self.ir, v, meet);
729 }
730 let name_type = type_expr::find_or_create_name(self.ir, "/name");
732 if let Inst::Var(v) = self.ir.get(args[1]) {
733 let v = *v;
734 state.add_or_refine_with_ir(self.ir, v, name_type);
735 }
736 }
737 Ok(state)
738 }
739
740 fn infer_match_field(&mut self, args: &[InstId], mut state: InferState) -> Result<InferState> {
742 if args.len() != 3 {
743 return Ok(state);
744 }
745 let var_ranges = state.as_map();
746 let scrutinee_type = self.bound_of_arg(args[0], &var_ranges);
747
748 let field_name_id = match self.ir.get(args[1]) {
750 Inst::Name(n) => Some(*n),
751 _ => None,
752 };
753
754 if let Some(field) = field_name_id
755 && (type_expr::is_struct_type(self.ir, scrutinee_type)
756 || type_expr::is_tagged_union_type(self.ir, scrutinee_type)
757 || type_expr::is_union_type(self.ir, scrutinee_type))
758 && let Some(field_type) =
759 type_expr::struct_type_field_deep(self.ir, scrutinee_type, field)
760 {
761 let ctx = TypeContext::default();
763 let value_bound = self.bound_of_arg(args[2], &state.as_map());
764 let meet = type_expr::lower_bound(self.ir, &ctx, &[value_bound, field_type]);
765 if !type_expr::is_empty_type(self.ir, meet)
766 && let Inst::Var(v) = self.ir.get(args[2])
767 {
768 let v = *v;
769 state.add_or_refine_with_ir(self.ir, v, meet);
770 }
771 }
772 let any = type_expr::find_or_create_name(self.ir, "/any");
774 if let Inst::Var(v) = self.ir.get(args[0]) {
775 let v = *v;
776 state.add_or_refine_with_ir(self.ir, v, any);
777 }
778 let name_type = type_expr::find_or_create_name(self.ir, "/name");
780 if let Inst::Var(v) = self.ir.get(args[1]) {
781 let v = *v;
782 state.add_or_refine_with_ir(self.ir, v, name_type);
783 }
784 Ok(state)
785 }
786
787 fn infer_match_entry(&mut self, args: &[InstId], mut state: InferState) -> Result<InferState> {
789 if args.len() != 3 {
790 return Ok(state);
791 }
792 let var_ranges = state.as_map();
793 let map_type = self.bound_of_arg(args[0], &var_ranges);
794
795 if type_expr::is_map_type(self.ir, map_type)
796 && let Some((key_type, val_type)) = type_expr::map_type_args(self.ir, map_type)
797 {
798 let ctx = TypeContext::default();
799
800 let key_bound = self.bound_of_arg(args[1], &state.as_map());
802 let key_meet = type_expr::lower_bound(self.ir, &ctx, &[key_bound, key_type]);
803 if !type_expr::is_empty_type(self.ir, key_meet)
804 && let Inst::Var(v) = self.ir.get(args[1])
805 {
806 let v = *v;
807 state.add_or_refine_with_ir(self.ir, v, key_meet);
808 }
809
810 let val_bound = self.bound_of_arg(args[2], &state.as_map());
812 let val_meet = type_expr::lower_bound(self.ir, &ctx, &[val_bound, val_type]);
813 if !type_expr::is_empty_type(self.ir, val_meet)
814 && let Inst::Var(v) = self.ir.get(args[2])
815 {
816 let v = *v;
817 state.add_or_refine_with_ir(self.ir, v, val_meet);
818 }
819 }
820 Ok(state)
821 }
822
823 fn infer_list_member(&mut self, args: &[InstId], mut state: InferState) -> Result<InferState> {
825 if args.len() != 2 {
826 return Ok(state);
827 }
828 let var_ranges = state.as_map();
829 let list_type = self.bound_of_arg(args[1], &var_ranges);
830
831 if type_expr::is_list_type(self.ir, list_type)
832 && let Some(elem_type) = type_expr::list_type_arg(self.ir, list_type)
833 {
834 let ctx = TypeContext::default();
835 let elem_bound = self.bound_of_arg(args[0], &state.as_map());
836 let meet = type_expr::lower_bound(self.ir, &ctx, &[elem_bound, elem_type]);
837 if !type_expr::is_empty_type(self.ir, meet)
838 && let Inst::Var(v) = self.ir.get(args[0])
839 {
840 let v = *v;
841 state.add_or_refine_with_ir(self.ir, v, meet);
842 }
843 }
844 Ok(state)
845 }
846
847 fn bound_of_arg(&mut self, arg: InstId, var_ranges: &FxHashMap<NameId, InstId>) -> InstId {
849 match self.ir.get(arg) {
850 Inst::Var(v) => {
851 let v = *v;
852 if let Some(&range) = var_ranges.get(&v) {
853 range
854 } else {
855 type_expr::find_or_create_name(self.ir, "/any")
856 }
857 }
858 Inst::Number(_) => type_expr::find_or_create_name(self.ir, "/number"),
859 Inst::Float(_) => type_expr::find_or_create_name(self.ir, "/float64"),
860 Inst::String(_) => type_expr::find_or_create_name(self.ir, "/string"),
861 Inst::Bool(_) => type_expr::find_or_create_name(self.ir, "/bool"),
862 Inst::Time(_) => type_expr::find_or_create_name(self.ir, "/time"),
863 Inst::Duration(_) => type_expr::find_or_create_name(self.ir, "/duration"),
864 Inst::Bytes(_) => type_expr::find_or_create_name(self.ir, "/bytes"),
865 Inst::Name(n) => {
866 let name = self.ir.resolve_name(*n).to_string();
867 let prefix = self.name_trie.prefix_name(&name);
868 type_expr::find_or_create_name(self.ir, &prefix)
869 }
870 Inst::List(elems) => {
871 let elems = elems.clone();
872 if elems.is_empty() {
873 let bot = type_expr::find_or_create_name(self.ir, "/bot");
874 return type_expr::new_list_type(self.ir, bot);
875 }
876 let ctx = TypeContext::default();
877 let elem_types: Vec<InstId> = elems
878 .iter()
879 .map(|e| self.bound_of_arg(*e, var_ranges))
880 .collect();
881 let elem_type = type_expr::upper_bound(self.ir, &ctx, &elem_types);
882 type_expr::new_list_type(self.ir, elem_type)
883 }
884 Inst::Map { keys, values } => {
885 let keys = keys.clone();
886 let values = values.clone();
887 let ctx = TypeContext::default();
888 let key_types: Vec<InstId> = keys
889 .iter()
890 .map(|k| self.bound_of_arg(*k, var_ranges))
891 .collect();
892 let val_types: Vec<InstId> = values
893 .iter()
894 .map(|v| self.bound_of_arg(*v, var_ranges))
895 .collect();
896 let kt = type_expr::upper_bound(self.ir, &ctx, &key_types);
897 let vt = type_expr::upper_bound(self.ir, &ctx, &val_types);
898 type_expr::new_map_type(self.ir, kt, vt)
899 }
900 Inst::Struct { fields, values } => {
901 let fields = fields.clone();
902 let values = values.clone();
903 let mut args = Vec::new();
904 for (f, v) in fields.iter().zip(values.iter()) {
905 let fname = self.ir.resolve_name(*f).to_string();
906 let fname_id = type_expr::find_or_create_name(self.ir, &fname);
907 let vtype = self.bound_of_arg(*v, var_ranges);
908 args.push(fname_id);
909 args.push(vtype);
910 }
911 type_expr::new_struct_type(self.ir, args)
912 }
913 Inst::ApplyFn { function, args } => {
914 let fname = self.ir.resolve_name(*function).to_string();
915 let args = args.clone();
916 self.bound_of_apply_fn(&fname, &args, var_ranges)
917 }
918 _ => type_expr::find_or_create_name(self.ir, "/any"),
919 }
920 }
921
922 fn bound_of_apply_fn(
924 &mut self,
925 fname: &str,
926 args: &[InstId],
927 var_ranges: &FxHashMap<NameId, InstId>,
928 ) -> InstId {
929 match fname {
930 "fn:list" => {
931 if args.is_empty() {
932 let bot = type_expr::find_or_create_name(self.ir, "/bot");
933 return type_expr::new_list_type(self.ir, bot);
934 }
935 let ctx = TypeContext::default();
936 let arg_types: Vec<InstId> = args
937 .iter()
938 .map(|a| self.bound_of_arg(*a, var_ranges))
939 .collect();
940 let elem = type_expr::upper_bound(self.ir, &ctx, &arg_types);
941 type_expr::new_list_type(self.ir, elem)
942 }
943 "fn:map" => {
944 let ctx = TypeContext::default();
945 let mut key_types = Vec::new();
946 let mut val_types = Vec::new();
947 let mut i = 0;
948 while i + 1 < args.len() {
949 key_types.push(self.bound_of_arg(args[i], var_ranges));
950 val_types.push(self.bound_of_arg(args[i + 1], var_ranges));
951 i += 2;
952 }
953 let kt = type_expr::upper_bound(self.ir, &ctx, &key_types);
954 let vt = type_expr::upper_bound(self.ir, &ctx, &val_types);
955 type_expr::new_map_type(self.ir, kt, vt)
956 }
957 "fn:struct" => {
958 let mut struct_args = Vec::new();
959 let mut i = 0;
960 while i + 1 < args.len() {
961 struct_args.push(args[i]); struct_args.push(self.bound_of_arg(args[i + 1], var_ranges));
963 i += 2;
964 }
965 type_expr::new_struct_type(self.ir, struct_args)
966 }
967 "fn:tuple" => {
968 let arg_types: Vec<InstId> = args
969 .iter()
970 .map(|a| self.bound_of_arg(*a, var_ranges))
971 .collect();
972 type_expr::new_tuple_type(self.ir, arg_types)
973 }
974 "fn:struct_get" if args.len() == 2 => {
975 let struct_type = self.bound_of_arg(args[0], var_ranges);
976 if let Inst::Name(n) = self.ir.get(args[1]) {
977 let field = *n;
978 if let Some(ft) = type_expr::struct_type_field_deep(self.ir, struct_type, field)
979 {
980 return ft;
981 }
982 }
983 type_expr::find_or_create_name(self.ir, "/any")
984 }
985 "fn:plus" | "fn:minus" | "fn:mult" | "fn:div" => {
986 type_expr::find_or_create_name(self.ir, "/number")
987 }
988 "fn:float_plus" | "fn:float_mult" | "fn:float_div" => {
989 type_expr::find_or_create_name(self.ir, "/float64")
990 }
991 "fn:string:concat" | "fn:string:replace" => {
992 type_expr::find_or_create_name(self.ir, "/string")
993 }
994 "fn:count" | "fn:sum" | "fn:max" | "fn:min" => {
995 type_expr::find_or_create_name(self.ir, "/number")
996 }
997 "fn:list:len" | "fn:len" | "fn:map:len" | "fn:struct:len" => {
998 type_expr::find_or_create_name(self.ir, "/number")
999 }
1000 "fn:sqrt" => type_expr::find_or_create_name(self.ir, "/float64"),
1001 "fn:list:get" if args.len() == 2 => {
1002 let list_type = self.bound_of_arg(args[0], var_ranges);
1004 match type_expr::apply_fn_args(self.ir, list_type) {
1005 Some(inner)
1006 if type_expr::apply_fn_name(self.ir, list_type)
1007 == Some(type_expr::FN_LIST)
1008 && inner.len() == 1 =>
1009 {
1010 inner[0]
1011 }
1012 _ => type_expr::find_or_create_name(self.ir, "/any"),
1013 }
1014 }
1015 "fn:list:append" if args.len() == 2 => {
1016 let list_type = self.bound_of_arg(args[0], var_ranges);
1018 let new_elem = self.bound_of_arg(args[1], var_ranges);
1019 let old_elem = match type_expr::apply_fn_args(self.ir, list_type) {
1020 Some(inner)
1021 if type_expr::apply_fn_name(self.ir, list_type)
1022 == Some(type_expr::FN_LIST)
1023 && inner.len() == 1 =>
1024 {
1025 inner[0]
1026 }
1027 _ => type_expr::find_or_create_name(self.ir, "/any"),
1028 };
1029 let ctx = TypeContext::default();
1030 let elem = type_expr::upper_bound(self.ir, &ctx, &[old_elem, new_elem]);
1031 type_expr::new_list_type(self.ir, elem)
1032 }
1033 "fn:collect" | "fn:collect_distinct" => {
1034 if args.len() == 1 {
1035 let elem_type = self.bound_of_arg(args[0], var_ranges);
1036 type_expr::new_list_type(self.ir, elem_type)
1037 } else {
1038 let any = type_expr::find_or_create_name(self.ir, "/any");
1039 type_expr::new_list_type(self.ir, any)
1040 }
1041 }
1042 _ => type_expr::find_or_create_name(self.ir, "/any"),
1043 }
1044 }
1045
1046 fn atom_predicate(&self, atom_id: InstId) -> Option<NameId> {
1049 if let Inst::Atom { predicate, .. } = self.ir.get(atom_id) {
1050 Some(*predicate)
1051 } else {
1052 None
1053 }
1054 }
1055
1056 fn atom_args(&self, atom_id: InstId) -> Vec<InstId> {
1057 if let Inst::Atom { args, .. } = self.ir.get(atom_id) {
1058 args.clone()
1059 } else {
1060 Vec::new()
1061 }
1062 }
1063
1064 fn describe_inst(&self, id: InstId) -> String {
1066 match self.ir.get(id) {
1067 Inst::Name(n) => self.ir.resolve_name(*n).to_string(),
1068 Inst::Number(n) => n.to_string(),
1069 Inst::Float(f) => f.to_string(),
1070 Inst::String(s) => format!("{:?}", self.ir.resolve_string(*s)),
1071 Inst::Bool(b) => b.to_string(),
1072 Inst::Var(v) => self.ir.resolve_name(*v).to_string(),
1073 Inst::ApplyFn { function, args } => {
1074 let fname = self.ir.resolve_name(*function);
1075 let arg_strs: Vec<String> = args.iter().map(|a| self.describe_inst(*a)).collect();
1076 format!("{}({})", fname, arg_strs.join(", "))
1077 }
1078 _ => format!("inst#{}", id.index()),
1079 }
1080 }
1081}
1082
1083struct InferState {
1091 used_vars: Vec<NameId>,
1093 var_types: Vec<InstId>,
1095}
1096
1097impl InferState {
1098 fn new() -> Self {
1099 Self {
1100 used_vars: Vec::new(),
1101 var_types: Vec::new(),
1102 }
1103 }
1104
1105 fn add_or_refine_with_ir(&mut self, ir: &mut Ir, var: NameId, tpe: InstId) {
1107 if let Some(idx) = self.used_vars.iter().position(|v| *v == var) {
1108 let existing = self.var_types[idx];
1110 let ctx = TypeContext::default();
1111 let meet = type_expr::lower_bound(ir, &ctx, &[existing, tpe]);
1112 if !type_expr::is_empty_type(ir, meet) {
1113 self.var_types[idx] = meet;
1114 }
1115 } else {
1117 self.used_vars.push(var);
1118 self.var_types.push(tpe);
1119 }
1120 }
1121
1122 fn set_var(&mut self, var: NameId, tpe: InstId) {
1124 if let Some(idx) = self.used_vars.iter().position(|v| *v == var) {
1125 self.var_types[idx] = tpe;
1126 }
1127 }
1128
1129 fn as_map(&self) -> FxHashMap<NameId, InstId> {
1131 self.used_vars
1132 .iter()
1133 .zip(self.var_types.iter())
1134 .map(|(v, t)| (*v, *t))
1135 .collect()
1136 }
1137}
1138
1139#[cfg(test)]
1140mod tests {
1141 use super::*;
1142 use crate::LoweringContext;
1143 use mangle_ast as ast;
1144 use mangle_parse::Parser;
1145
1146 fn check(source: &str) -> Result<()> {
1148 let arena = ast::Arena::new_with_global_interner();
1149 let mut parser = Parser::new(&arena, source.as_bytes(), "test");
1150 parser.next_token().unwrap();
1151 let unit = parser.parse_unit().unwrap();
1152 let ctx = LoweringContext::new(&arena);
1153 let mut ir = ctx.lower_unit(&unit);
1154 let mut checker = BoundsChecker::new(&mut ir);
1155 checker.check()
1156 }
1157
1158 #[test]
1163 fn check_valid_fact() {
1164 let arena = ast::Arena::new_with_global_interner();
1165
1166 let foo_sym = arena.predicate_sym("foo", Some(1));
1168 let var_x = arena.variable("X");
1169 let atom_foo_x = arena.atom(foo_sym, &[var_x]);
1170 let num_type = arena.const_(arena.name("/number"));
1171 let bound_decl = ast::BoundDecl {
1172 base_terms: arena.alloc_slice_copy(&[num_type]),
1173 };
1174 let decl = ast::Decl {
1175 atom: atom_foo_x,
1176 descr: &[],
1177 bounds: Some(arena.alloc_slice_copy(&[arena.alloc(bound_decl)])),
1178 constraints: None,
1179 is_temporal: false,
1180 };
1181
1182 let const_42 = arena.const_(ast::Const::Number(42));
1184 let atom_foo_42 = arena.atom(foo_sym, &[const_42]);
1185 let clause = ast::Clause {
1186 head: atom_foo_42,
1187 head_time: None,
1188 premises: &[],
1189 transform: &[],
1190 };
1191
1192 let unit = ast::Unit {
1193 decls: arena.alloc_slice_copy(&[&decl]),
1194 clauses: arena.alloc_slice_copy(&[&clause]),
1195 };
1196
1197 let ctx = LoweringContext::new(&arena);
1198 let mut ir = ctx.lower_unit(&unit);
1199 let mut checker = BoundsChecker::new(&mut ir);
1200 assert!(checker.check().is_ok());
1201 }
1202
1203 #[test]
1204 fn check_invalid_fact_type_mismatch() {
1205 let arena = ast::Arena::new_with_global_interner();
1206
1207 let foo_sym = arena.predicate_sym("foo", Some(1));
1209 let var_x = arena.variable("X");
1210 let atom_foo_x = arena.atom(foo_sym, &[var_x]);
1211 let num_type = arena.const_(arena.name("/number"));
1212 let bound_decl = ast::BoundDecl {
1213 base_terms: arena.alloc_slice_copy(&[num_type]),
1214 };
1215 let decl = ast::Decl {
1216 atom: atom_foo_x,
1217 descr: &[],
1218 bounds: Some(arena.alloc_slice_copy(&[arena.alloc(bound_decl)])),
1219 constraints: None,
1220 is_temporal: false,
1221 };
1222
1223 let const_str = arena.const_(ast::Const::String("hello"));
1225 let atom_foo_bad = arena.atom(foo_sym, &[const_str]);
1226 let clause = ast::Clause {
1227 head: atom_foo_bad,
1228 head_time: None,
1229 premises: &[],
1230 transform: &[],
1231 };
1232
1233 let unit = ast::Unit {
1234 decls: arena.alloc_slice_copy(&[&decl]),
1235 clauses: arena.alloc_slice_copy(&[&clause]),
1236 };
1237
1238 let ctx = LoweringContext::new(&arena);
1239 let mut ir = ctx.lower_unit(&unit);
1240 let mut checker = BoundsChecker::new(&mut ir);
1241 let result = checker.check();
1242 assert!(result.is_err(), "expected type mismatch error");
1243 }
1244
1245 #[test]
1246 fn check_valid_rule() {
1247 let arena = ast::Arena::new_with_global_interner();
1248
1249 let src_sym = arena.predicate_sym("src", Some(1));
1251 let var_x = arena.variable("X");
1252 let atom_src_x = arena.atom(src_sym, &[var_x]);
1253 let num_type = arena.const_(arena.name("/number"));
1254 let bound_decl = ast::BoundDecl {
1255 base_terms: arena.alloc_slice_copy(&[num_type]),
1256 };
1257 let decl_src = ast::Decl {
1258 atom: atom_src_x,
1259 descr: &[],
1260 bounds: Some(arena.alloc_slice_copy(&[arena.alloc(bound_decl)])),
1261 constraints: None,
1262 is_temporal: false,
1263 };
1264
1265 let dst_sym = arena.predicate_sym("dst", Some(1));
1267 let var_y = arena.variable("Y");
1268 let atom_dst_y = arena.atom(dst_sym, &[var_y]);
1269 let num_type2 = arena.const_(arena.name("/number"));
1270 let bound_decl2 = ast::BoundDecl {
1271 base_terms: arena.alloc_slice_copy(&[num_type2]),
1272 };
1273 let decl_dst = ast::Decl {
1274 atom: atom_dst_y,
1275 descr: &[],
1276 bounds: Some(arena.alloc_slice_copy(&[arena.alloc(bound_decl2)])),
1277 constraints: None,
1278 is_temporal: false,
1279 };
1280
1281 let var_x2 = arena.variable("X");
1283 let head = arena.atom(dst_sym, &[var_x2]);
1284 let var_x3 = arena.variable("X");
1285 let body = arena.atom(src_sym, &[var_x3]);
1286 let clause = ast::Clause {
1287 head,
1288 head_time: None,
1289 premises: arena.alloc_slice_copy(&[arena.alloc(ast::Term::Atom(body))]),
1290 transform: &[],
1291 };
1292
1293 let unit = ast::Unit {
1294 decls: arena.alloc_slice_copy(&[&decl_src, &decl_dst]),
1295 clauses: arena.alloc_slice_copy(&[&clause]),
1296 };
1297
1298 let ctx = LoweringContext::new(&arena);
1299 let mut ir = ctx.lower_unit(&unit);
1300 let mut checker = BoundsChecker::new(&mut ir);
1301 assert!(checker.check().is_ok());
1302 }
1303
1304 #[test]
1305 fn check_arity_mismatch() {
1306 let arena = ast::Arena::new_with_global_interner();
1307
1308 let foo_sym = arena.predicate_sym("foo", Some(1));
1310 let var_x = arena.variable("X");
1311 let atom_foo_x = arena.atom(foo_sym, &[var_x]);
1312 let num_type = arena.const_(arena.name("/number"));
1313 let bound_decl = ast::BoundDecl {
1314 base_terms: arena.alloc_slice_copy(&[num_type]),
1315 };
1316 let decl = ast::Decl {
1317 atom: atom_foo_x,
1318 descr: &[],
1319 bounds: Some(arena.alloc_slice_copy(&[arena.alloc(bound_decl)])),
1320 constraints: None,
1321 is_temporal: false,
1322 };
1323
1324 let const_42 = arena.const_(ast::Const::Number(42));
1326 let const_43 = arena.const_(ast::Const::Number(43));
1327 let atom_foo_bad = arena.atom(foo_sym, &[const_42, const_43]);
1328 let clause = ast::Clause {
1329 head: atom_foo_bad,
1330 head_time: None,
1331 premises: &[],
1332 transform: &[],
1333 };
1334
1335 let unit = ast::Unit {
1336 decls: arena.alloc_slice_copy(&[&decl]),
1337 clauses: arena.alloc_slice_copy(&[&clause]),
1338 };
1339
1340 let ctx = LoweringContext::new(&arena);
1341 let mut ir = ctx.lower_unit(&unit);
1342 let mut checker = BoundsChecker::new(&mut ir);
1343 let result = checker.check();
1344 assert!(result.is_err());
1345 }
1346
1347 #[test]
1352 fn multiple_alternatives_first_matches() {
1353 assert!(
1355 check(
1356 r#"
1357 Decl pair(X, Y) bound [/number, /number] bound [/string, /string].
1358 pair(42, 99).
1359 "#
1360 )
1361 .is_ok()
1362 );
1363 }
1364
1365 #[test]
1366 fn multiple_alternatives_second_matches() {
1367 assert!(
1369 check(
1370 r#"
1371 Decl pair(X, Y) bound [/number, /number] bound [/string, /string].
1372 pair("a", "b").
1373 "#
1374 )
1375 .is_ok()
1376 );
1377 }
1378
1379 #[test]
1380 fn multiple_alternatives_none_matches() {
1381 assert!(
1383 check(
1384 r#"
1385 Decl pair(X, Y) bound [/number, /number] bound [/string, /string].
1386 pair(42, "b").
1387 "#
1388 )
1389 .is_err()
1390 );
1391 }
1392
1393 #[test]
1398 fn rule_infers_type_from_premise() {
1399 assert!(
1401 check(
1402 r#"
1403 Decl src(X) bound [/number].
1404 Decl dst(X) bound [/number].
1405 dst(X) :- src(X).
1406 "#
1407 )
1408 .is_ok()
1409 );
1410 }
1411
1412 #[test]
1413 fn rule_type_mismatch_from_premise() {
1414 assert!(
1416 check(
1417 r#"
1418 Decl src(X) bound [/string].
1419 Decl dst(X) bound [/number].
1420 dst(X) :- src(X).
1421 "#
1422 )
1423 .is_err()
1424 );
1425 }
1426
1427 #[test]
1432 fn two_premises_refine_variable() {
1433 assert!(
1436 check(
1437 r#"
1438 Decl wide(X) bound [fn:Union(/number, /string)].
1439 Decl narrow(X) bound [/number].
1440 Decl result(X) bound [/number].
1441 result(X) :- wide(X), narrow(X).
1442 "#
1443 )
1444 .is_ok()
1445 );
1446 }
1447
1448 #[test]
1449 fn two_premises_refine_to_incompatible() {
1450 assert!(
1454 check(
1455 r#"
1456 Decl src1(X) bound [/string].
1457 Decl src2(X) bound [/number].
1458 Decl dst(X) bound [/number].
1459 dst(X) :- src1(X), src2(X).
1460 "#
1461 )
1462 .is_err()
1463 );
1464 }
1465
1466 #[test]
1471 fn polymorphic_identity_number() {
1472 assert!(
1474 check(
1475 r#"
1476 Decl pair(X, Y) bound [T, T].
1477 pair(42, 99).
1478 "#
1479 )
1480 .is_ok()
1481 );
1482 }
1483
1484 #[test]
1485 fn polymorphic_identity_string() {
1486 assert!(
1488 check(
1489 r#"
1490 Decl pair(X, Y) bound [T, T].
1491 pair("a", "b").
1492 "#
1493 )
1494 .is_ok()
1495 );
1496 }
1497
1498 #[test]
1499 fn polymorphic_rule_with_inferred_type() {
1500 assert!(
1503 check(
1504 r#"
1505 Decl src(X) bound [/number].
1506 Decl dst(X) bound [T].
1507 dst(X) :- src(X).
1508 "#
1509 )
1510 .is_ok()
1511 );
1512 }
1513
1514 #[test]
1519 fn cross_predicate_inference_basic() {
1520 assert!(
1523 check(
1524 r#"
1525 Decl src(X) bound [/number].
1526 Decl dst(X) bound [/number].
1527 helper(X) :- src(X).
1528 dst(X) :- helper(X).
1529 "#
1530 )
1531 .is_ok()
1532 );
1533 }
1534
1535 #[test]
1536 fn cross_predicate_inference_type_mismatch() {
1537 assert!(
1539 check(
1540 r#"
1541 Decl src(X) bound [/string].
1542 Decl dst(X) bound [/number].
1543 helper(X) :- src(X).
1544 dst(X) :- helper(X).
1545 "#
1546 )
1547 .is_err()
1548 );
1549 }
1550
1551 #[test]
1552 fn cross_predicate_inference_chain() {
1553 assert!(
1555 check(
1556 r#"
1557 Decl src(X) bound [/number].
1558 Decl dst(X) bound [/number].
1559 mid(X) :- src(X).
1560 dst(X) :- mid(X).
1561 "#
1562 )
1563 .is_ok()
1564 );
1565 }
1566
1567 #[test]
1572 fn equality_binds_variable() {
1573 assert!(
1575 check(
1576 r#"
1577 Decl src(X) bound [/string].
1578 Decl dst(X) bound [/string].
1579 dst(X) :- src(X), X = "hello".
1580 "#
1581 )
1582 .is_ok()
1583 );
1584 }
1585
1586 #[test]
1587 fn inequality_refines_variable() {
1588 assert!(
1590 check(
1591 r#"
1592 Decl src(X) bound [/string].
1593 Decl dst(X) bound [/string].
1594 dst(X) :- src(X), X != "bad".
1595 "#
1596 )
1597 .is_ok()
1598 );
1599 }
1600
1601 #[test]
1606 fn transform_arithmetic() {
1607 assert!(
1609 check(
1610 r#"
1611 Decl src(X) bound [/number].
1612 Decl dst(X, Y) bound [/number, /number].
1613 dst(X, Y) :- src(X) |> let Y = fn:plus(X, 1).
1614 "#
1615 )
1616 .is_ok()
1617 );
1618 }
1619
1620 #[test]
1621 fn transform_string_concat() {
1622 assert!(
1624 check(
1625 r#"
1626 Decl src(X) bound [/string].
1627 Decl dst(X, Y) bound [/string, /string].
1628 dst(X, Y) :- src(X) |> let Y = fn:string:concat(X, "!").
1629 "#
1630 )
1631 .is_ok()
1632 );
1633 }
1634
1635 #[test]
1636 fn transform_type_mismatch() {
1637 assert!(
1639 check(
1640 r#"
1641 Decl src(X) bound [/number].
1642 Decl dst(X, Y) bound [/number, /string].
1643 dst(X, Y) :- src(X) |> let Y = fn:plus(X, 1).
1644 "#
1645 )
1646 .is_err()
1647 );
1648 }
1649
1650 #[test]
1655 fn undeclared_predicate_passes() {
1656 assert!(
1658 check(
1659 r#"
1660 foo(1).
1661 bar(X) :- foo(X).
1662 "#
1663 )
1664 .is_ok()
1665 );
1666 }
1667
1668 #[test]
1673 fn arity_mismatch_facts() {
1674 let result = check(
1676 r#"
1677 p(1).
1678 p(2, 3).
1679 "#,
1680 );
1681 assert!(result.is_err(), "expected arity error, got: {:?}", result);
1682 let msg = result.unwrap_err().to_string();
1683 assert!(
1684 msg.contains("inconsistent arity"),
1685 "error should mention 'inconsistent arity': {}",
1686 msg
1687 );
1688 assert!(
1689 msg.contains("p"),
1690 "error should mention predicate name: {}",
1691 msg
1692 );
1693 }
1694
1695 #[test]
1696 fn arity_mismatch_fact_and_rule() {
1697 let result = check(
1699 r#"
1700 p(1).
1701 p(X, Y) :- q(X, Y).
1702 q(1, 2).
1703 "#,
1704 );
1705 assert!(result.is_err(), "expected arity error, got: {:?}", result);
1706 let msg = result.unwrap_err().to_string();
1707 assert!(
1708 msg.contains("inconsistent arity"),
1709 "error should mention 'inconsistent arity': {}",
1710 msg
1711 );
1712 }
1713
1714 #[test]
1715 fn arity_mismatch_two_rules() {
1716 let result = check(
1718 r#"
1719 p(X) :- q(X).
1720 p(X, Y) :- r(X, Y).
1721 q(1).
1722 r(1, 2).
1723 "#,
1724 );
1725 assert!(result.is_err(), "expected arity error, got: {:?}", result);
1726 let msg = result.unwrap_err().to_string();
1727 assert!(
1728 msg.contains("inconsistent arity"),
1729 "error should mention 'inconsistent arity': {}",
1730 msg
1731 );
1732 }
1733
1734 #[test]
1735 fn consistent_arity_passes() {
1736 assert!(
1738 check(
1739 r#"
1740 p(1).
1741 p(2).
1742 q(X) :- p(X).
1743 "#
1744 )
1745 .is_ok()
1746 );
1747 }
1748
1749 #[test]
1750 fn consistent_arity_rules_passes() {
1751 assert!(
1753 check(
1754 r#"
1755 p(1, 2).
1756 p(X, Y) :- q(X), r(Y).
1757 q(1).
1758 r(2).
1759 "#
1760 )
1761 .is_ok()
1762 );
1763 }
1764
1765 #[test]
1766 fn arity_mismatch_undeclared_predicates() {
1767 let result = check(
1769 r#"
1770 edge(1, 2).
1771 edge(3, 4, 5).
1772 "#,
1773 );
1774 assert!(result.is_err(), "expected arity error, got: {:?}", result);
1775 let msg = result.unwrap_err().to_string();
1776 assert!(
1777 msg.contains("inconsistent arity"),
1778 "error should mention 'inconsistent arity': {}",
1779 msg
1780 );
1781 }
1782}