1use crate::ast::{
4 ArithExpr, Atom, BodyLiteral, Comparison, Constraint, FuncBody, FuncDef, IsExpr, Term, Univ,
5};
6use crate::function::{is_builtin, FunctionError, FunctionRegistry};
7use std::collections::{HashMap, HashSet};
8
9const GENERATED_FUNCTION_VARIABLE_PREFIX: &str = "__XLOG_FUNCTION_";
10
11pub(crate) fn generated_function_variable_name(
12 function_name: &str,
13 source_name: &str,
14 counter: usize,
15) -> String {
16 format!(
17 "{GENERATED_FUNCTION_VARIABLE_PREFIX}{}_{}_{}",
18 function_name.to_ascii_uppercase(),
19 source_name,
20 counter
21 )
22}
23
24pub(crate) fn generated_function_variable_source<'a>(
25 generated_name: &'a str,
26 function_name: &str,
27) -> Option<&'a str> {
28 let prefix = format!(
29 "{GENERATED_FUNCTION_VARIABLE_PREFIX}{}_",
30 function_name.to_ascii_uppercase()
31 );
32 let (source_name, counter) = generated_name.strip_prefix(&prefix)?.rsplit_once('_')?;
33 (!source_name.is_empty()
34 && !counter.is_empty()
35 && counter.chars().all(|character| character.is_ascii_digit()))
36 .then_some(source_name)
37}
38
39#[derive(Debug)]
40struct ExpandedExpression {
41 generated_literals: Vec<BodyLiteral>,
42 expression: ArithExpr,
43}
44
45fn inline_trailing_predicate_result_binding(
46 mut literals: Vec<BodyLiteral>,
47 result: ArithExpr,
48) -> (Vec<BodyLiteral>, ArithExpr) {
49 let ArithExpr::Variable(result_name) = &result else {
50 return (literals, result);
51 };
52 let Some(BodyLiteral::IsExpr(binding)) = literals.last() else {
53 return (literals, result);
54 };
55 let ArithExpr::Variable(source_name) = &binding.expr else {
56 return (literals, result);
57 };
58 if !predicate_prefix_binds_variable(&literals[..literals.len() - 1], source_name)
59 || binding.target != *result_name
60 || binding
61 .expr
62 .variables()
63 .into_iter()
64 .any(|name| name == result_name)
65 || literals[..literals.len() - 1].iter().any(|literal| {
66 literal
67 .variables()
68 .into_iter()
69 .any(|name| name == result_name)
70 })
71 {
72 return (literals, result);
73 }
74
75 let Some(BodyLiteral::IsExpr(binding)) = literals.pop() else {
76 unreachable!("trailing predicate result binding was checked above")
77 };
78 (literals, binding.expr)
79}
80
81fn predicate_prefix_binds_variable(literals: &[BodyLiteral], wanted: &str) -> bool {
82 let mut bound = HashSet::new();
83 for literal in literals {
84 match literal {
85 BodyLiteral::Positive(atom) => {
86 bound.extend(atom.variables().into_iter().map(ToOwned::to_owned));
87 }
88 BodyLiteral::IsExpr(binding)
89 if binding
90 .expr
91 .variables()
92 .into_iter()
93 .all(|name| bound.contains(name)) =>
94 {
95 bound.insert(binding.target.clone());
96 }
97 BodyLiteral::Negated(_)
98 | BodyLiteral::Epistemic(_)
99 | BodyLiteral::Comparison(_)
100 | BodyLiteral::IsExpr(_)
101 | BodyLiteral::Univ(_) => {}
102 }
103 }
104 bound.contains(wanted)
105}
106
107impl ExpandedExpression {
108 fn value(expression: ArithExpr) -> Self {
109 Self {
110 generated_literals: Vec::new(),
111 expression,
112 }
113 }
114}
115
116type BinaryExpressionConstructor = fn(Box<ArithExpr>, Box<ArithExpr>) -> ArithExpr;
117
118enum ExpansionTask {
119 Expression {
120 expression: ArithExpr,
121 subst: HashMap<String, ArithExpr>,
122 in_conditional_branch: bool,
123 },
124 FinishCall {
125 name: String,
126 argument_count: usize,
127 in_conditional_branch: bool,
128 },
129 EnterFunction {
130 name: String,
131 args: Vec<ArithExpr>,
132 in_conditional_branch: bool,
133 },
134 FunctionBody {
135 function_name: String,
136 body: FuncBody,
137 subst: HashMap<String, ArithExpr>,
138 in_conditional_branch: bool,
139 },
140 LeaveFunction,
141 PrependGenerated {
142 literals: Vec<BodyLiteral>,
143 },
144 FinishBinary {
145 constructor: BinaryExpressionConstructor,
146 },
147 FinishAbs,
148 FinishCast(xlog_core::ScalarType),
149 FinishConditional(crate::ast::CompOp),
150 PredicateLiteral(PreparedPredicateLiteral),
151 FinishPredicateBinding {
152 target: String,
153 },
154 FinishPredicateBody {
155 literal_count: usize,
156 result: ArithExpr,
157 },
158}
159
160enum PreparedPredicateLiteral {
161 Literal(BodyLiteral),
162 Binding {
163 target: String,
164 expression: ArithExpr,
165 subst: HashMap<String, ArithExpr>,
166 },
167}
168
169enum TermSubstitutionTask<'a> {
170 Term(&'a Term),
171 FinishList(usize),
172 FinishCons,
173 FinishCompound {
174 functor: String,
175 argument_count: usize,
176 },
177}
178
179pub struct ExpansionContext<'a> {
181 registry: &'a FunctionRegistry,
182 depth: u32,
183 max_depth: u32,
184 fresh_counter: usize,
185}
186
187impl<'a> ExpansionContext<'a> {
188 pub fn new(registry: &'a FunctionRegistry, max_depth: u32) -> Self {
190 Self {
191 registry,
192 depth: 0,
193 max_depth,
194 fresh_counter: 0,
195 }
196 }
197
198 pub fn expand_call(
203 &mut self,
204 name: &str,
205 args: &[ArithExpr],
206 ) -> Result<ArithExpr, FunctionError> {
207 if !self.registry.contains(name) {
208 return Err(FunctionError::UndefinedFunction {
209 name: name.to_string(),
210 });
211 }
212
213 let call = ArithExpr::FuncCall {
214 name: name.to_string(),
215 args: args.to_vec(),
216 };
217 let mut used_variables = call
218 .variables()
219 .into_iter()
220 .map(ToOwned::to_owned)
221 .collect();
222 let expanded =
223 self.expand_expr_for_rule(&call, &HashMap::new(), &mut used_variables, false)?;
224 if expanded.generated_literals.is_empty() {
225 Ok(expanded.expression)
226 } else {
227 Err(FunctionError::PredicateBodyRequiresRuleContext {
228 name: name.to_string(),
229 })
230 }
231 }
232
233 fn check_arity(func: &FuncDef, args: &[ArithExpr]) -> Result<(), FunctionError> {
234 if func.params.len() == args.len() {
235 return Ok(());
236 }
237 Err(FunctionError::ArityMismatch {
238 name: func.name.clone(),
239 expected: func.params.len(),
240 received: args.len(),
241 })
242 }
243
244 fn fresh_variable(
245 &mut self,
246 function_name: &str,
247 source_name: &str,
248 used_variables: &mut HashSet<String>,
249 ) -> String {
250 loop {
251 let candidate =
252 generated_function_variable_name(function_name, source_name, self.fresh_counter);
253 self.fresh_counter += 1;
254 if used_variables.insert(candidate.clone()) {
255 return candidate;
256 }
257 }
258 }
259
260 fn prepare_predicate_func(
262 &mut self,
263 function_name: &str,
264 result: String,
265 body: Vec<BodyLiteral>,
266 mut subst: HashMap<String, ArithExpr>,
267 used_variables: &mut HashSet<String>,
268 ) -> Result<(Vec<PreparedPredicateLiteral>, ArithExpr), FunctionError> {
269 let parameter_names: HashSet<String> = subst.keys().cloned().collect();
270 let mut local_names = Vec::new();
271 let mut seen_locals = HashSet::new();
272
273 if !parameter_names.contains(&result) && seen_locals.insert(result.clone()) {
274 local_names.push(result.clone());
275 }
276 for literal in &body {
277 for variable in literal.variables() {
278 if !parameter_names.contains(variable) && seen_locals.insert(variable.to_string()) {
279 local_names.push(variable.to_string());
280 }
281 }
282 }
283
284 for local in local_names {
285 let fresh = self.fresh_variable(function_name, &local, used_variables);
286 subst.insert(local, ArithExpr::Variable(fresh));
287 }
288
289 let substituted_body = body
290 .into_iter()
291 .map(|literal| {
292 if let BodyLiteral::IsExpr(binding) = literal {
293 Ok(PreparedPredicateLiteral::Binding {
294 target: self.substitute_binding_target(
295 function_name,
296 &binding.target,
297 &subst,
298 )?,
299 expression: binding.expr,
300 subst: subst.clone(),
301 })
302 } else {
303 self.substitute_literal(function_name, &literal, &subst)
304 .map(PreparedPredicateLiteral::Literal)
305 }
306 })
307 .collect::<Result<Vec<_>, _>>()?;
308 let expression = subst
309 .get(&result)
310 .cloned()
311 .unwrap_or(ArithExpr::Variable(result));
312 Ok((substituted_body, expression))
313 }
314
315 fn substitute_literal(
316 &self,
317 function_name: &str,
318 lit: &BodyLiteral,
319 subst: &HashMap<String, ArithExpr>,
320 ) -> Result<BodyLiteral, FunctionError> {
321 Ok(match lit {
322 BodyLiteral::Positive(atom) => {
323 BodyLiteral::Positive(self.substitute_atom(function_name, atom, subst)?)
324 }
325 BodyLiteral::Negated(atom) => {
326 BodyLiteral::Negated(self.substitute_atom(function_name, atom, subst)?)
327 }
328 BodyLiteral::Epistemic(lit) => BodyLiteral::Epistemic(crate::ast::EpistemicLiteral {
329 op: lit.op,
330 negated: lit.negated,
331 atom: self.substitute_atom(function_name, &lit.atom, subst)?,
332 }),
333 BodyLiteral::Comparison(cmp) => BodyLiteral::Comparison(Comparison {
334 left: self.substitute_term(function_name, &cmp.left, subst)?,
335 op: cmp.op,
336 right: self.substitute_term(function_name, &cmp.right, subst)?,
337 }),
338 BodyLiteral::IsExpr(_) => unreachable!("predicate bindings are prepared separately"),
339 BodyLiteral::Univ(univ) => BodyLiteral::Univ(Univ {
340 term: self.substitute_term(function_name, &univ.term, subst)?,
341 parts: self.substitute_term(function_name, &univ.parts, subst)?,
342 }),
343 })
344 }
345
346 fn substitute_atom(
347 &self,
348 function_name: &str,
349 atom: &Atom,
350 subst: &HashMap<String, ArithExpr>,
351 ) -> Result<Atom, FunctionError> {
352 Ok(Atom {
353 predicate: atom.predicate.clone(),
354 terms: atom
355 .terms
356 .iter()
357 .map(|term| self.substitute_term(function_name, term, subst))
358 .collect::<Result<Vec<_>, _>>()?,
359 })
360 }
361
362 fn substitute_term(
363 &self,
364 function_name: &str,
365 term: &Term,
366 subst: &HashMap<String, ArithExpr>,
367 ) -> Result<Term, FunctionError> {
368 let mut tasks = vec![TermSubstitutionTask::Term(term)];
369 let mut values = Vec::new();
370
371 while let Some(task) = tasks.pop() {
372 match task {
373 TermSubstitutionTask::Term(term) => match term {
374 Term::Variable(name) => values.push(match subst.get(name) {
375 Some(ArithExpr::Variable(new_name)) => Term::Variable(new_name.clone()),
376 Some(ArithExpr::Integer(value)) => Term::Integer(*value),
377 Some(ArithExpr::Float(value)) => Term::Float(*value),
378 Some(_) => {
379 return Err(FunctionError::UnsupportedPredicateTermArgument {
380 name: function_name.to_string(),
381 parameter: name.clone(),
382 });
383 }
384 None => Term::Variable(name.clone()),
385 }),
386 Term::List(items) => {
387 tasks.push(TermSubstitutionTask::FinishList(items.len()));
388 for item in items.iter().rev() {
389 tasks.push(TermSubstitutionTask::Term(item));
390 }
391 }
392 Term::Cons { head, tail } => {
393 tasks.push(TermSubstitutionTask::FinishCons);
394 tasks.push(TermSubstitutionTask::Term(tail));
395 tasks.push(TermSubstitutionTask::Term(head));
396 }
397 Term::Compound { functor, args } => {
398 tasks.push(TermSubstitutionTask::FinishCompound {
399 functor: functor.clone(),
400 argument_count: args.len(),
401 });
402 for argument in args.iter().rev() {
403 tasks.push(TermSubstitutionTask::Term(argument));
404 }
405 }
406 Term::Aggregate(aggregate) => {
407 values.push(Term::Aggregate(crate::ast::AggExpr {
408 op: aggregate.op,
409 variable: self.substitute_binding_target(
410 function_name,
411 &aggregate.variable,
412 subst,
413 )?,
414 }));
415 }
416 Term::Anonymous => values.push(Term::Anonymous),
417 Term::Integer(value) => values.push(Term::Integer(*value)),
418 Term::Float(value) => values.push(Term::Float(*value)),
419 Term::String(value) => values.push(Term::String(value.clone())),
420 Term::Symbol(value) => values.push(Term::Symbol(*value)),
421 Term::PredRef(value) => values.push(Term::PredRef(value.clone())),
422 },
423 TermSubstitutionTask::FinishList(item_count) => {
424 let start = values
425 .len()
426 .checked_sub(item_count)
427 .expect("list items produce substituted terms");
428 let items = values.split_off(start);
429 values.push(Term::List(items));
430 }
431 TermSubstitutionTask::FinishCons => {
432 let tail = values.pop().expect("cons tail produces a substituted term");
433 let head = values.pop().expect("cons head produces a substituted term");
434 values.push(Term::Cons {
435 head: Box::new(head),
436 tail: Box::new(tail),
437 });
438 }
439 TermSubstitutionTask::FinishCompound {
440 functor,
441 argument_count,
442 } => {
443 let start = values
444 .len()
445 .checked_sub(argument_count)
446 .expect("compound arguments produce substituted terms");
447 let args = values.split_off(start);
448 values.push(Term::Compound { functor, args });
449 }
450 }
451 }
452
453 let substituted = values.pop().expect("term substitution produces one term");
454 debug_assert!(values.is_empty());
455 Ok(substituted)
456 }
457
458 fn substitute_binding_target(
459 &self,
460 function_name: &str,
461 variable: &str,
462 subst: &HashMap<String, ArithExpr>,
463 ) -> Result<String, FunctionError> {
464 match subst.get(variable) {
465 Some(ArithExpr::Variable(new_name)) => Ok(new_name.clone()),
466 Some(_) => Err(FunctionError::InvalidPredicateBindingTarget {
467 name: function_name.to_string(),
468 parameter: variable.to_string(),
469 }),
470 None => Ok(variable.to_string()),
471 }
472 }
473
474 fn expand_literal_for_rule(
475 &mut self,
476 literal: &BodyLiteral,
477 used_variables: &mut HashSet<String>,
478 ) -> Result<Vec<BodyLiteral>, FunctionError> {
479 let BodyLiteral::IsExpr(binding) = literal else {
480 return Ok(vec![literal.clone()]);
481 };
482
483 let expanded =
484 self.expand_expr_for_rule(&binding.expr, &HashMap::new(), used_variables, false)?;
485 let mut literals = expanded.generated_literals;
486 literals.push(BodyLiteral::IsExpr(IsExpr {
487 target: binding.target.clone(),
488 expr: expanded.expression,
489 }));
490 Ok(literals)
491 }
492
493 fn expand_expr_for_rule(
494 &mut self,
495 expression: &ArithExpr,
496 subst: &HashMap<String, ArithExpr>,
497 used_variables: &mut HashSet<String>,
498 in_conditional_branch: bool,
499 ) -> Result<ExpandedExpression, FunctionError> {
500 let saved_depth = self.depth;
501 let saved_fresh_counter = self.fresh_counter;
502 let saved_used_variables = used_variables.clone();
503 let result =
504 self.run_expansion_machine(expression, subst, used_variables, in_conditional_branch);
505 self.depth = saved_depth;
506 if result.is_err() {
507 self.fresh_counter = saved_fresh_counter;
508 *used_variables = saved_used_variables;
509 }
510 result
511 }
512
513 fn run_expansion_machine(
514 &mut self,
515 expression: &ArithExpr,
516 subst: &HashMap<String, ArithExpr>,
517 used_variables: &mut HashSet<String>,
518 in_conditional_branch: bool,
519 ) -> Result<ExpandedExpression, FunctionError> {
520 let mut tasks = vec![ExpansionTask::Expression {
521 expression: expression.clone(),
522 subst: subst.clone(),
523 in_conditional_branch,
524 }];
525 let mut values = Vec::new();
526 let mut predicate_literal_values: Vec<Vec<BodyLiteral>> = Vec::new();
527
528 while let Some(task) = tasks.pop() {
529 match task {
530 ExpansionTask::Expression {
531 expression,
532 subst,
533 in_conditional_branch,
534 } => match expression {
535 ArithExpr::Variable(name) => values.push(ExpandedExpression::value(
536 subst
537 .get(&name)
538 .cloned()
539 .unwrap_or(ArithExpr::Variable(name)),
540 )),
541 ArithExpr::Integer(_) | ArithExpr::Float(_) => {
542 values.push(ExpandedExpression::value(expression));
543 }
544 ArithExpr::FuncCall { name, args } => {
545 if let Some(func) = self.registry.get(&name) {
546 Self::check_arity(func, &args)?;
547 }
548 tasks.push(ExpansionTask::FinishCall {
549 name,
550 argument_count: args.len(),
551 in_conditional_branch,
552 });
553 for argument in args.into_iter().rev() {
554 tasks.push(ExpansionTask::Expression {
555 expression: argument,
556 subst: subst.clone(),
557 in_conditional_branch,
558 });
559 }
560 }
561 ArithExpr::Add(left, right) => {
562 Self::schedule_binary(
563 &mut tasks,
564 *left,
565 *right,
566 subst,
567 in_conditional_branch,
568 ArithExpr::Add,
569 );
570 }
571 ArithExpr::Sub(left, right) => {
572 Self::schedule_binary(
573 &mut tasks,
574 *left,
575 *right,
576 subst,
577 in_conditional_branch,
578 ArithExpr::Sub,
579 );
580 }
581 ArithExpr::Mul(left, right) => {
582 Self::schedule_binary(
583 &mut tasks,
584 *left,
585 *right,
586 subst,
587 in_conditional_branch,
588 ArithExpr::Mul,
589 );
590 }
591 ArithExpr::Div(left, right) => {
592 Self::schedule_binary(
593 &mut tasks,
594 *left,
595 *right,
596 subst,
597 in_conditional_branch,
598 ArithExpr::Div,
599 );
600 }
601 ArithExpr::Mod(left, right) => {
602 Self::schedule_binary(
603 &mut tasks,
604 *left,
605 *right,
606 subst,
607 in_conditional_branch,
608 ArithExpr::Mod,
609 );
610 }
611 ArithExpr::Min(left, right) => {
612 Self::schedule_binary(
613 &mut tasks,
614 *left,
615 *right,
616 subst,
617 in_conditional_branch,
618 ArithExpr::Min,
619 );
620 }
621 ArithExpr::Max(left, right) => {
622 Self::schedule_binary(
623 &mut tasks,
624 *left,
625 *right,
626 subst,
627 in_conditional_branch,
628 ArithExpr::Max,
629 );
630 }
631 ArithExpr::Pow(left, right) => {
632 Self::schedule_binary(
633 &mut tasks,
634 *left,
635 *right,
636 subst,
637 in_conditional_branch,
638 ArithExpr::Pow,
639 );
640 }
641 ArithExpr::Abs(inner) => {
642 tasks.push(ExpansionTask::FinishAbs);
643 tasks.push(ExpansionTask::Expression {
644 expression: *inner,
645 subst,
646 in_conditional_branch,
647 });
648 }
649 ArithExpr::Cast(inner, target) => {
650 tasks.push(ExpansionTask::FinishCast(target));
651 tasks.push(ExpansionTask::Expression {
652 expression: *inner,
653 subst,
654 in_conditional_branch,
655 });
656 }
657 ArithExpr::Conditional {
658 cond_left,
659 cond_op,
660 cond_right,
661 then_expr,
662 else_expr,
663 } => {
664 tasks.push(ExpansionTask::FinishConditional(cond_op));
665 tasks.push(ExpansionTask::Expression {
666 expression: *else_expr,
667 subst: subst.clone(),
668 in_conditional_branch: true,
669 });
670 tasks.push(ExpansionTask::Expression {
671 expression: *then_expr,
672 subst: subst.clone(),
673 in_conditional_branch: true,
674 });
675 tasks.push(ExpansionTask::Expression {
676 expression: *cond_right,
677 subst: subst.clone(),
678 in_conditional_branch,
679 });
680 tasks.push(ExpansionTask::Expression {
681 expression: *cond_left,
682 subst,
683 in_conditional_branch,
684 });
685 }
686 },
687 ExpansionTask::FinishCall {
688 name,
689 argument_count,
690 in_conditional_branch,
691 } => {
692 let start = values
693 .len()
694 .checked_sub(argument_count)
695 .expect("function arguments produce expansion values");
696 let argument_values = values.split_off(start);
697 let mut generated_literals = Vec::new();
698 let mut args = Vec::with_capacity(argument_count);
699 for argument in argument_values {
700 generated_literals.extend(argument.generated_literals);
701 args.push(argument.expression);
702 }
703 if self.registry.contains(&name) {
704 tasks.push(ExpansionTask::PrependGenerated {
705 literals: generated_literals,
706 });
707 tasks.push(ExpansionTask::EnterFunction {
708 name,
709 args,
710 in_conditional_branch,
711 });
712 } else if is_builtin(&name) {
713 values.push(ExpandedExpression {
714 generated_literals,
715 expression: ArithExpr::FuncCall { name, args },
716 });
717 } else {
718 return Err(FunctionError::UndefinedFunction { name });
719 }
720 }
721 ExpansionTask::EnterFunction {
722 name,
723 args,
724 in_conditional_branch,
725 } => {
726 let func =
727 self.registry.get(&name).cloned().ok_or_else(|| {
728 FunctionError::UndefinedFunction { name: name.clone() }
729 })?;
730 Self::check_arity(&func, &args)?;
731 if self.depth >= self.max_depth {
732 return Err(FunctionError::MaxRecursionDepth {
733 name,
734 depth: self.max_depth,
735 });
736 }
737 if in_conditional_branch && matches!(func.body, FuncBody::Predicate { .. }) {
738 return Err(FunctionError::PredicateCallInConditionalBranch { name });
739 }
740
741 self.depth += 1;
742 let subst = func
743 .params
744 .iter()
745 .zip(&args)
746 .map(|(param, argument)| (param.name.clone(), argument.clone()))
747 .collect();
748 tasks.push(ExpansionTask::LeaveFunction);
749 tasks.push(ExpansionTask::FunctionBody {
750 function_name: func.name,
751 body: func.body,
752 subst,
753 in_conditional_branch,
754 });
755 }
756 ExpansionTask::FunctionBody {
757 function_name,
758 body,
759 subst,
760 in_conditional_branch,
761 } => match body {
762 FuncBody::Arithmetic(expression) => {
763 tasks.push(ExpansionTask::Expression {
764 expression,
765 subst,
766 in_conditional_branch,
767 });
768 }
769 FuncBody::Predicate { result, body } => {
770 if in_conditional_branch {
771 return Err(FunctionError::PredicateCallInConditionalBranch {
772 name: function_name,
773 });
774 }
775 let (literals, result) = self.prepare_predicate_func(
776 &function_name,
777 result,
778 body,
779 subst,
780 used_variables,
781 )?;
782 tasks.push(ExpansionTask::FinishPredicateBody {
783 literal_count: literals.len(),
784 result,
785 });
786 for literal in literals.into_iter().rev() {
787 tasks.push(ExpansionTask::PredicateLiteral(literal));
788 }
789 }
790 FuncBody::Conditional(conditional) => {
791 tasks.push(ExpansionTask::FinishConditional(conditional.cond_op));
792 tasks.push(ExpansionTask::FunctionBody {
793 function_name: function_name.clone(),
794 body: *conditional.else_branch,
795 subst: subst.clone(),
796 in_conditional_branch: true,
797 });
798 tasks.push(ExpansionTask::FunctionBody {
799 function_name,
800 body: *conditional.then_branch,
801 subst: subst.clone(),
802 in_conditional_branch: true,
803 });
804 tasks.push(ExpansionTask::Expression {
805 expression: conditional.cond_right,
806 subst: subst.clone(),
807 in_conditional_branch,
808 });
809 tasks.push(ExpansionTask::Expression {
810 expression: conditional.cond_left,
811 subst,
812 in_conditional_branch,
813 });
814 }
815 },
816 ExpansionTask::LeaveFunction => {
817 debug_assert!(self.depth > 0);
818 self.depth -= 1;
819 }
820 ExpansionTask::PrependGenerated { mut literals } => {
821 let mut expanded = values
822 .pop()
823 .expect("function call produces an expansion value");
824 literals.append(&mut expanded.generated_literals);
825 expanded.generated_literals = literals;
826 values.push(expanded);
827 }
828 ExpansionTask::FinishBinary { constructor } => {
829 let right = values.pop().expect("right expression is expanded");
830 let mut left = values.pop().expect("left expression is expanded");
831 left.generated_literals.extend(right.generated_literals);
832 left.expression =
833 constructor(Box::new(left.expression), Box::new(right.expression));
834 values.push(left);
835 }
836 ExpansionTask::FinishAbs => {
837 let mut expanded = values.pop().expect("absolute-value operand is expanded");
838 expanded.expression = ArithExpr::Abs(Box::new(expanded.expression));
839 values.push(expanded);
840 }
841 ExpansionTask::FinishCast(target) => {
842 let mut expanded = values.pop().expect("cast operand is expanded");
843 expanded.expression = ArithExpr::Cast(Box::new(expanded.expression), target);
844 values.push(expanded);
845 }
846 ExpansionTask::FinishConditional(cond_op) => {
847 let else_value = values.pop().expect("else branch is expanded");
848 let then_value = values.pop().expect("then branch is expanded");
849 let right = values.pop().expect("condition right side is expanded");
850 let mut left = values.pop().expect("condition left side is expanded");
851 left.generated_literals.extend(right.generated_literals);
852 left.generated_literals
853 .extend(then_value.generated_literals);
854 left.generated_literals
855 .extend(else_value.generated_literals);
856 left.expression = ArithExpr::Conditional {
857 cond_left: Box::new(left.expression),
858 cond_op,
859 cond_right: Box::new(right.expression),
860 then_expr: Box::new(then_value.expression),
861 else_expr: Box::new(else_value.expression),
862 };
863 values.push(left);
864 }
865 ExpansionTask::PredicateLiteral(literal) => match literal {
866 PreparedPredicateLiteral::Binding {
867 target,
868 expression,
869 subst,
870 } => {
871 tasks.push(ExpansionTask::FinishPredicateBinding { target });
872 tasks.push(ExpansionTask::Expression {
873 expression,
874 subst,
875 in_conditional_branch: false,
876 });
877 }
878 PreparedPredicateLiteral::Literal(literal) => {
879 predicate_literal_values.push(vec![literal]);
880 }
881 },
882 ExpansionTask::FinishPredicateBinding { target } => {
883 let mut expanded = values
884 .pop()
885 .expect("predicate-body binding expression is expanded");
886 expanded
887 .generated_literals
888 .push(BodyLiteral::IsExpr(IsExpr {
889 target,
890 expr: expanded.expression,
891 }));
892 predicate_literal_values.push(expanded.generated_literals);
893 }
894 ExpansionTask::FinishPredicateBody {
895 literal_count,
896 result,
897 } => {
898 let start = predicate_literal_values
899 .len()
900 .checked_sub(literal_count)
901 .expect("predicate literals produce expansion values");
902 let generated_literals = predicate_literal_values
903 .split_off(start)
904 .into_iter()
905 .flatten()
906 .collect();
907 let (generated_literals, result) =
908 inline_trailing_predicate_result_binding(generated_literals, result);
909 values.push(ExpandedExpression {
910 generated_literals,
911 expression: result,
912 });
913 }
914 }
915 }
916
917 debug_assert!(predicate_literal_values.is_empty());
918 let expanded = values
919 .pop()
920 .expect("expression expansion produces one value");
921 debug_assert!(values.is_empty());
922 Ok(expanded)
923 }
924
925 fn schedule_binary(
926 tasks: &mut Vec<ExpansionTask>,
927 left: ArithExpr,
928 right: ArithExpr,
929 subst: HashMap<String, ArithExpr>,
930 in_conditional_branch: bool,
931 constructor: BinaryExpressionConstructor,
932 ) {
933 tasks.push(ExpansionTask::FinishBinary { constructor });
934 tasks.push(ExpansionTask::Expression {
935 expression: right,
936 subst: subst.clone(),
937 in_conditional_branch,
938 });
939 tasks.push(ExpansionTask::Expression {
940 expression: left,
941 subst,
942 in_conditional_branch,
943 });
944 }
945
946 #[allow(dead_code)]
948 pub(crate) fn is_predicate_func(&self, name: &str) -> bool {
949 self.registry
950 .get(name)
951 .map(|f| matches!(f.body, FuncBody::Predicate { .. }))
952 .unwrap_or(false)
953 }
954}
955
956use crate::ast::{Program, Rule};
957
958pub fn expand_program_functions(
963 program: &Program,
964 max_depth: u32,
965) -> Result<Program, FunctionError> {
966 if program.functions.is_empty() {
968 return Ok(program.clone());
969 }
970 expand_program_functions_impl(program, max_depth)
971}
972
973pub fn expand_program_functions_owned(
977 program: Program,
978 max_depth: u32,
979) -> Result<Program, FunctionError> {
980 if program.functions.is_empty() {
981 return Ok(program);
982 }
983 expand_program_functions_impl(&program, max_depth)
984}
985
986fn expand_program_functions_impl(
987 program: &Program,
988 max_depth: u32,
989) -> Result<Program, FunctionError> {
990 let mut registry = FunctionRegistry::new();
991 for function in &program.functions {
992 registry.register(function.clone())?;
993 }
994 let mut ctx = ExpansionContext::new(®istry, max_depth);
995
996 let expanded_rules: Result<Vec<Rule>, FunctionError> = program
998 .rules
999 .iter()
1000 .map(|rule| expand_rule_functions(&mut ctx, rule))
1001 .collect();
1002 let expanded_constraints: Result<Vec<Constraint>, FunctionError> = program
1003 .constraints
1004 .iter()
1005 .map(|constraint| expand_constraint_functions(&mut ctx, constraint))
1006 .collect();
1007
1008 Ok(Program {
1009 rules: expanded_rules?,
1010 directives: program.directives.clone(),
1011 queries: program.queries.clone(),
1012 predicates: program.predicates.clone(),
1013 constraints: expanded_constraints?,
1014 authored_constraint_source_bound: program.authored_constraint_source_bound,
1015 imports: program.imports.clone(),
1016 functions: program.functions.clone(),
1017 domains: program.domains.clone(),
1018 prob_facts: program.prob_facts.clone(),
1019 annotated_disjunctions: program.annotated_disjunctions.clone(),
1020 evidence: program.evidence.clone(),
1021 prob_queries: program.prob_queries.clone(),
1022 neural_predicates: program.neural_predicates.clone(),
1023 learnable_rules: program.learnable_rules.clone(),
1024 })
1025}
1026
1027fn expand_rule_functions(ctx: &mut ExpansionContext, rule: &Rule) -> Result<Rule, FunctionError> {
1029 let mut used_variables: HashSet<String> = rule
1030 .head
1031 .variables()
1032 .into_iter()
1033 .chain(rule.body.iter().flat_map(BodyLiteral::variables))
1034 .map(ToOwned::to_owned)
1035 .collect();
1036 let expanded_body = expand_body_functions(ctx, &rule.body, &mut used_variables)?;
1037
1038 Ok(Rule {
1039 head: rule.head.clone(),
1040 body: expanded_body,
1041 })
1042}
1043
1044fn expand_constraint_functions(
1045 ctx: &mut ExpansionContext,
1046 constraint: &Constraint,
1047) -> Result<Constraint, FunctionError> {
1048 let mut used_variables: HashSet<String> = constraint
1049 .body
1050 .iter()
1051 .flat_map(BodyLiteral::variables)
1052 .map(ToOwned::to_owned)
1053 .collect();
1054 Ok(Constraint {
1055 authored_index: constraint.authored_index,
1056 body: expand_body_functions(ctx, &constraint.body, &mut used_variables)?,
1057 })
1058}
1059
1060fn expand_body_functions(
1061 ctx: &mut ExpansionContext,
1062 body: &[BodyLiteral],
1063 used_variables: &mut HashSet<String>,
1064) -> Result<Vec<BodyLiteral>, FunctionError> {
1065 let mut expanded_body = Vec::new();
1066 for literal in body {
1067 expanded_body.extend(ctx.expand_literal_for_rule(literal, used_variables)?);
1068 }
1069 Ok(expanded_body)
1070}
1071
1072#[cfg(test)]
1073mod tests {
1074 use super::*;
1075 use crate::ast::{FuncDef, FuncParam};
1076
1077 #[test]
1078 fn test_simple_expansion() {
1079 let mut reg = FunctionRegistry::new();
1080
1081 let double = FuncDef {
1083 name: "double".to_string(),
1084 params: vec![FuncParam {
1085 name: "X".to_string(),
1086 typ: None,
1087 }],
1088 return_type: None,
1089 body: FuncBody::Arithmetic(ArithExpr::Add(
1090 Box::new(ArithExpr::Variable("X".to_string())),
1091 Box::new(ArithExpr::Variable("X".to_string())),
1092 )),
1093 is_private: false,
1094 };
1095 reg.register(double).unwrap();
1096
1097 let mut ctx = ExpansionContext::new(®, 100);
1098
1099 let result = ctx.expand_call("double", &[ArithExpr::Integer(5)]).unwrap();
1101
1102 match result {
1103 ArithExpr::Add(l, r) => {
1104 assert!(matches!(*l, ArithExpr::Integer(5)));
1105 assert!(matches!(*r, ArithExpr::Integer(5)));
1106 }
1107 _ => panic!("Expected Add expression"),
1108 }
1109 }
1110
1111 #[test]
1112 fn test_nested_expansion() {
1113 let mut reg = FunctionRegistry::new();
1114
1115 let double = FuncDef {
1117 name: "double".to_string(),
1118 params: vec![FuncParam {
1119 name: "X".to_string(),
1120 typ: None,
1121 }],
1122 return_type: None,
1123 body: FuncBody::Arithmetic(ArithExpr::Add(
1124 Box::new(ArithExpr::Variable("X".to_string())),
1125 Box::new(ArithExpr::Variable("X".to_string())),
1126 )),
1127 is_private: false,
1128 };
1129
1130 let quadruple = FuncDef {
1132 name: "quadruple".to_string(),
1133 params: vec![FuncParam {
1134 name: "X".to_string(),
1135 typ: None,
1136 }],
1137 return_type: None,
1138 body: FuncBody::Arithmetic(ArithExpr::FuncCall {
1139 name: "double".to_string(),
1140 args: vec![ArithExpr::FuncCall {
1141 name: "double".to_string(),
1142 args: vec![ArithExpr::Variable("X".to_string())],
1143 }],
1144 }),
1145 is_private: false,
1146 };
1147
1148 reg.register(double).unwrap();
1149 reg.register(quadruple).unwrap();
1150
1151 let mut ctx = ExpansionContext::new(®, 100);
1152
1153 let result = ctx
1155 .expand_call("quadruple", &[ArithExpr::Integer(2)])
1156 .unwrap();
1157
1158 match &result {
1160 ArithExpr::Add(l, r) => {
1161 assert!(matches!(l.as_ref(), ArithExpr::Add(_, _)));
1162 assert!(matches!(r.as_ref(), ArithExpr::Add(_, _)));
1163 }
1164 _ => panic!("Expected nested Add expression, got {:?}", result),
1165 }
1166 }
1167
1168 #[test]
1169 fn test_max_recursion_depth() {
1170 let mut reg = FunctionRegistry::new();
1171
1172 let infinite = FuncDef {
1174 name: "infinite".to_string(),
1175 params: vec![FuncParam {
1176 name: "X".to_string(),
1177 typ: None,
1178 }],
1179 return_type: None,
1180 body: FuncBody::Arithmetic(ArithExpr::FuncCall {
1181 name: "infinite".to_string(),
1182 args: vec![ArithExpr::Variable("X".to_string())],
1183 }),
1184 is_private: false,
1185 };
1186 reg.register(infinite).unwrap();
1187
1188 let mut ctx = ExpansionContext::new(®, 10);
1189
1190 let result = ctx.expand_call("infinite", &[ArithExpr::Integer(1)]);
1191 assert!(matches!(
1192 result,
1193 Err(FunctionError::MaxRecursionDepth { .. })
1194 ));
1195 }
1196
1197 #[test]
1198 fn test_undefined_function() {
1199 let reg = FunctionRegistry::new();
1200 let mut ctx = ExpansionContext::new(®, 100);
1201
1202 let result = ctx.expand_call("undefined", &[ArithExpr::Integer(1)]);
1203 assert!(matches!(
1204 result,
1205 Err(FunctionError::UndefinedFunction { .. })
1206 ));
1207 }
1208
1209 #[test]
1210 fn test_builtin_function_passthrough() {
1211 let mut reg = FunctionRegistry::new();
1212
1213 let abs_x = FuncDef {
1215 name: "abs_x".to_string(),
1216 params: vec![FuncParam {
1217 name: "X".to_string(),
1218 typ: None,
1219 }],
1220 return_type: None,
1221 body: FuncBody::Arithmetic(ArithExpr::FuncCall {
1222 name: "abs".to_string(),
1223 args: vec![ArithExpr::Variable("X".to_string())],
1224 }),
1225 is_private: false,
1226 };
1227 reg.register(abs_x).unwrap();
1228
1229 let mut ctx = ExpansionContext::new(®, 100);
1230
1231 let result = ctx.expand_call("abs_x", &[ArithExpr::Integer(-5)]).unwrap();
1232
1233 match result {
1235 ArithExpr::FuncCall { name, args } => {
1236 assert_eq!(name, "abs");
1237 assert_eq!(args.len(), 1);
1238 assert!(matches!(args[0], ArithExpr::Integer(-5)));
1239 }
1240 _ => panic!("Expected FuncCall for builtin"),
1241 }
1242 }
1243
1244 #[test]
1245 fn test_variable_substitution() {
1246 let mut reg = FunctionRegistry::new();
1247
1248 let add = FuncDef {
1250 name: "add".to_string(),
1251 params: vec![
1252 FuncParam {
1253 name: "X".to_string(),
1254 typ: None,
1255 },
1256 FuncParam {
1257 name: "Y".to_string(),
1258 typ: None,
1259 },
1260 ],
1261 return_type: None,
1262 body: FuncBody::Arithmetic(ArithExpr::Add(
1263 Box::new(ArithExpr::Variable("X".to_string())),
1264 Box::new(ArithExpr::Variable("Y".to_string())),
1265 )),
1266 is_private: false,
1267 };
1268 reg.register(add).unwrap();
1269
1270 let mut ctx = ExpansionContext::new(®, 100);
1271
1272 let result = ctx
1274 .expand_call("add", &[ArithExpr::Integer(3), ArithExpr::Integer(7)])
1275 .unwrap();
1276
1277 match result {
1278 ArithExpr::Add(l, r) => {
1279 assert!(matches!(*l, ArithExpr::Integer(3)));
1280 assert!(matches!(*r, ArithExpr::Integer(7)));
1281 }
1282 _ => panic!("Expected Add expression"),
1283 }
1284 }
1285
1286 #[test]
1287 fn test_expansion_with_variable_args() {
1288 let mut reg = FunctionRegistry::new();
1289
1290 let double = FuncDef {
1292 name: "double".to_string(),
1293 params: vec![FuncParam {
1294 name: "X".to_string(),
1295 typ: None,
1296 }],
1297 return_type: None,
1298 body: FuncBody::Arithmetic(ArithExpr::Add(
1299 Box::new(ArithExpr::Variable("X".to_string())),
1300 Box::new(ArithExpr::Variable("X".to_string())),
1301 )),
1302 is_private: false,
1303 };
1304 reg.register(double).unwrap();
1305
1306 let mut ctx = ExpansionContext::new(®, 100);
1307
1308 let result = ctx
1310 .expand_call("double", &[ArithExpr::Variable("Y".to_string())])
1311 .unwrap();
1312
1313 match result {
1314 ArithExpr::Add(l, r) => {
1315 assert!(matches!(l.as_ref(), ArithExpr::Variable(n) if n == "Y"));
1316 assert!(matches!(r.as_ref(), ArithExpr::Variable(n) if n == "Y"));
1317 }
1318 _ => panic!("Expected Add expression"),
1319 }
1320 }
1321
1322 #[test]
1323 fn test_predicate_func_expansion() {
1324 let func = FuncDef {
1328 name: "get_parent".to_string(),
1329 params: vec![FuncParam {
1330 name: "X".to_string(),
1331 typ: None,
1332 }],
1333 return_type: None,
1334 body: FuncBody::Predicate {
1335 result: "P".to_string(),
1336 body: vec![BodyLiteral::Positive(Atom {
1337 predicate: "parent".to_string(),
1338 terms: vec![
1339 Term::Variable("X".to_string()),
1340 Term::Variable("P".to_string()),
1341 ],
1342 })],
1343 },
1344 is_private: false,
1345 };
1346
1347 let mut reg = FunctionRegistry::new();
1348 reg.register(func).unwrap();
1349
1350 let mut ctx = ExpansionContext::new(®, 100);
1351
1352 let args = vec![ArithExpr::Variable("alice".to_string())];
1354 let mut used = HashSet::from(["alice".to_string()]);
1355 let expanded = ctx
1356 .expand_expr_for_rule(
1357 &ArithExpr::FuncCall {
1358 name: "get_parent".to_string(),
1359 args,
1360 },
1361 &HashMap::new(),
1362 &mut used,
1363 false,
1364 )
1365 .unwrap();
1366 let body = expanded.generated_literals;
1367 let ArithExpr::Variable(result) = expanded.expression else {
1368 panic!("Expected variable result")
1369 };
1370
1371 assert_ne!(result, "P");
1372 assert_eq!(body.len(), 1);
1373
1374 if let BodyLiteral::Positive(atom) = &body[0] {
1376 assert_eq!(atom.predicate, "parent");
1377 assert!(matches!(&atom.terms[0], Term::Variable(v) if v == "alice"));
1378 assert!(matches!(&atom.terms[1], Term::Variable(v) if v == &result));
1379 } else {
1380 panic!("Expected Positive literal");
1381 }
1382 }
1383
1384 #[test]
1385 fn test_predicate_func_with_constant_arg() {
1386 let func = FuncDef {
1390 name: "get_child".to_string(),
1391 params: vec![FuncParam {
1392 name: "P".to_string(),
1393 typ: None,
1394 }],
1395 return_type: None,
1396 body: FuncBody::Predicate {
1397 result: "C".to_string(),
1398 body: vec![BodyLiteral::Positive(Atom {
1399 predicate: "parent".to_string(),
1400 terms: vec![
1401 Term::Variable("C".to_string()),
1402 Term::Variable("P".to_string()),
1403 ],
1404 })],
1405 },
1406 is_private: false,
1407 };
1408
1409 let mut reg = FunctionRegistry::new();
1410 reg.register(func).unwrap();
1411
1412 let mut ctx = ExpansionContext::new(®, 100);
1413
1414 let args = vec![ArithExpr::Integer(42)];
1416 let mut used = HashSet::new();
1417 let expanded = ctx
1418 .expand_expr_for_rule(
1419 &ArithExpr::FuncCall {
1420 name: "get_child".to_string(),
1421 args,
1422 },
1423 &HashMap::new(),
1424 &mut used,
1425 false,
1426 )
1427 .unwrap();
1428 let body = expanded.generated_literals;
1429 let ArithExpr::Variable(result) = expanded.expression else {
1430 panic!("Expected variable result")
1431 };
1432
1433 assert_ne!(result, "C");
1434 assert_eq!(body.len(), 1);
1435
1436 if let BodyLiteral::Positive(atom) = &body[0] {
1438 assert_eq!(atom.predicate, "parent");
1439 assert!(matches!(&atom.terms[0], Term::Variable(v) if v == &result));
1440 assert!(matches!(&atom.terms[1], Term::Integer(42)));
1441 } else {
1442 panic!("Expected Positive literal");
1443 }
1444 }
1445
1446 #[test]
1447 fn test_predicate_func_multiple_body_literals() {
1448 let func = FuncDef {
1452 name: "get_grandparent".to_string(),
1453 params: vec![FuncParam {
1454 name: "X".to_string(),
1455 typ: None,
1456 }],
1457 return_type: None,
1458 body: FuncBody::Predicate {
1459 result: "G".to_string(),
1460 body: vec![
1461 BodyLiteral::Positive(Atom {
1462 predicate: "parent".to_string(),
1463 terms: vec![
1464 Term::Variable("X".to_string()),
1465 Term::Variable("P".to_string()),
1466 ],
1467 }),
1468 BodyLiteral::Positive(Atom {
1469 predicate: "parent".to_string(),
1470 terms: vec![
1471 Term::Variable("P".to_string()),
1472 Term::Variable("G".to_string()),
1473 ],
1474 }),
1475 ],
1476 },
1477 is_private: false,
1478 };
1479
1480 let mut reg = FunctionRegistry::new();
1481 reg.register(func).unwrap();
1482
1483 let mut ctx = ExpansionContext::new(®, 100);
1484
1485 let args = vec![ArithExpr::Variable("alice".to_string())];
1486 let mut used = HashSet::from(["alice".to_string()]);
1487 let expanded = ctx
1488 .expand_expr_for_rule(
1489 &ArithExpr::FuncCall {
1490 name: "get_grandparent".to_string(),
1491 args,
1492 },
1493 &HashMap::new(),
1494 &mut used,
1495 false,
1496 )
1497 .unwrap();
1498 let body = expanded.generated_literals;
1499 let ArithExpr::Variable(result) = expanded.expression else {
1500 panic!("Expected variable result")
1501 };
1502
1503 assert_ne!(result, "G");
1504 assert_eq!(body.len(), 2);
1505
1506 if let BodyLiteral::Positive(atom) = &body[0] {
1508 assert_eq!(atom.predicate, "parent");
1509 assert!(matches!(&atom.terms[0], Term::Variable(v) if v == "alice"));
1510 assert!(matches!(&atom.terms[1], Term::Variable(v) if v != "P"));
1511 } else {
1512 panic!("Expected Positive literal for first body");
1513 }
1514
1515 if let BodyLiteral::Positive(atom) = &body[1] {
1517 assert_eq!(atom.predicate, "parent");
1518 assert_eq!(atom.terms[0], body[0].atom().unwrap().terms[1]);
1519 assert!(matches!(&atom.terms[1], Term::Variable(v) if v == &result));
1520 } else {
1521 panic!("Expected Positive literal for second body");
1522 }
1523 }
1524
1525 #[test]
1526 fn test_is_predicate_func() {
1527 let mut reg = FunctionRegistry::new();
1528
1529 let arith_func = FuncDef {
1531 name: "double".to_string(),
1532 params: vec![FuncParam {
1533 name: "X".to_string(),
1534 typ: None,
1535 }],
1536 return_type: None,
1537 body: FuncBody::Arithmetic(ArithExpr::Add(
1538 Box::new(ArithExpr::Variable("X".to_string())),
1539 Box::new(ArithExpr::Variable("X".to_string())),
1540 )),
1541 is_private: false,
1542 };
1543
1544 let pred_func = FuncDef {
1546 name: "get_parent".to_string(),
1547 params: vec![FuncParam {
1548 name: "X".to_string(),
1549 typ: None,
1550 }],
1551 return_type: None,
1552 body: FuncBody::Predicate {
1553 result: "P".to_string(),
1554 body: vec![BodyLiteral::Positive(Atom {
1555 predicate: "parent".to_string(),
1556 terms: vec![
1557 Term::Variable("X".to_string()),
1558 Term::Variable("P".to_string()),
1559 ],
1560 })],
1561 },
1562 is_private: false,
1563 };
1564
1565 reg.register(arith_func).unwrap();
1566 reg.register(pred_func).unwrap();
1567
1568 let ctx = ExpansionContext::new(®, 100);
1569
1570 assert!(!ctx.is_predicate_func("double"));
1571 assert!(ctx.is_predicate_func("get_parent"));
1572 assert!(!ctx.is_predicate_func("nonexistent"));
1573 }
1574
1575 #[test]
1576 fn generated_variable_names_round_trip_underscored_identifiers() {
1577 let generated = generated_function_variable_name("get_parent", "Parent_Value", 42);
1578 assert_eq!(
1579 generated_function_variable_source(&generated, "get_parent"),
1580 Some("Parent_Value")
1581 );
1582 assert_eq!(generated, "__XLOG_FUNCTION_GET_PARENT_Parent_Value_42");
1583 assert!(generated_function_variable_source(
1584 "__XLOG_FUNCTION_GET_PARENT_Parent_Value_not_a_counter",
1585 "get_parent"
1586 )
1587 .is_none());
1588 }
1589}