1use crate::engine::Context;
2use crate::parsing::ast::{
3 self as ast, Constraint, EffectiveDate, LemmaData, LemmaRule, LemmaSpec, MetaValue, ParentType,
4 Value,
5};
6use crate::parsing::source::Source;
7use crate::planning::discovery;
8use crate::planning::semantics::{
9 self, conversion_target_to_semantic, primitive_boolean, primitive_date, primitive_duration,
10 primitive_number, primitive_ratio, primitive_text, primitive_time, value_to_semantic,
11 ArithmeticComputation, ComparisonComputation, DataDefinition, DataPath, Expression,
12 ExpressionKind, LemmaType, LiteralValue, PathSegment, ReferenceTarget, RulePath,
13 SemanticConversionTarget, TypeDefiningSpec, TypeExtends, TypeSpecification, ValueKind,
14};
15use crate::Error;
16use ast::DataValue as ParsedDataValue;
17use indexmap::IndexMap;
18use rust_decimal::Decimal;
19use std::cmp::Ordering;
20use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
21use std::fmt;
22use std::sync::Arc;
23
24type DataBindings = HashMap<Vec<String>, (BindingValue, Source)>;
33
34#[derive(Debug, Clone)]
43pub(crate) enum BindingValue {
44 Literal(ast::Value),
46 Reference {
48 target: ReferenceTarget,
49 constraints: Option<Vec<Constraint>>,
50 },
51}
52
53#[derive(Debug)]
54pub(crate) struct Graph {
55 main_spec: Arc<LemmaSpec>,
57 data: IndexMap<DataPath, DataDefinition>,
58 rules: BTreeMap<RulePath, RuleNode>,
59 execution_order: Vec<RulePath>,
60 reference_evaluation_order: Vec<DataPath>,
65}
66
67impl Graph {
68 pub(crate) fn data(&self) -> &IndexMap<DataPath, DataDefinition> {
69 &self.data
70 }
71
72 pub(crate) fn rules(&self) -> &BTreeMap<RulePath, RuleNode> {
73 &self.rules
74 }
75
76 pub(crate) fn rules_mut(&mut self) -> &mut BTreeMap<RulePath, RuleNode> {
77 &mut self.rules
78 }
79
80 pub(crate) fn execution_order(&self) -> &[RulePath] {
81 &self.execution_order
82 }
83
84 pub(crate) fn reference_evaluation_order(&self) -> &[DataPath] {
85 &self.reference_evaluation_order
86 }
87
88 pub(crate) fn main_spec(&self) -> &Arc<LemmaSpec> {
89 &self.main_spec
90 }
91
92 pub(crate) fn build_data(&self) -> IndexMap<DataPath, DataDefinition> {
95 struct PendingReference {
96 target: ReferenceTarget,
97 resolved_type: LemmaType,
98 local_constraints: Option<Vec<Constraint>>,
99 local_default: Option<ValueKind>,
100 }
101
102 let mut schema: HashMap<DataPath, LemmaType> = HashMap::new();
103 let mut declared_defaults: HashMap<DataPath, ValueKind> = HashMap::new();
104 let mut values: HashMap<DataPath, LiteralValue> = HashMap::new();
105 let mut spec_arcs: HashMap<DataPath, Arc<LemmaSpec>> = HashMap::new();
106 let mut references: HashMap<DataPath, PendingReference> = HashMap::new();
107
108 for (path, rfv) in self.data.iter() {
109 match rfv {
110 DataDefinition::Value { value, .. } => {
111 values.insert(path.clone(), value.clone());
112 schema.insert(path.clone(), value.lemma_type.clone());
113 }
114 DataDefinition::TypeDeclaration {
115 resolved_type,
116 declared_default,
117 ..
118 } => {
119 schema.insert(path.clone(), resolved_type.clone());
120 if let Some(dv) = declared_default {
121 declared_defaults.insert(path.clone(), dv.clone());
122 }
123 }
124 DataDefinition::SpecRef { spec: spec_arc, .. } => {
125 spec_arcs.insert(path.clone(), Arc::clone(spec_arc));
126 }
127 DataDefinition::Reference {
128 target,
129 resolved_type,
130 local_constraints,
131 local_default,
132 ..
133 } => {
134 schema.insert(path.clone(), resolved_type.clone());
135 references.insert(
136 path.clone(),
137 PendingReference {
138 target: target.clone(),
139 resolved_type: resolved_type.clone(),
140 local_constraints: local_constraints.clone(),
141 local_default: local_default.clone(),
142 },
143 );
144 }
145 }
146 }
147
148 for (path, schema_type) in &schema {
149 if values.contains_key(path) {
150 continue;
151 }
152 if references.contains_key(path) {
153 continue;
154 }
155 if let Some(declared) = declared_defaults.get(path) {
156 values.insert(
157 path.clone(),
158 LiteralValue {
159 value: declared.clone(),
160 lemma_type: schema_type.clone(),
161 },
162 );
163 }
164 }
165
166 for (path, value) in values.iter_mut() {
167 let Some(schema_type) = schema.get(path).cloned() else {
168 continue;
169 };
170 match Self::coerce_literal_to_schema_type(value, &schema_type) {
171 Ok(coerced) => *value = coerced,
172 Err(msg) => unreachable!("Data {} incompatible: {}", path, msg),
173 }
174 }
175
176 let mut data = IndexMap::new();
177 for (path, rfv) in &self.data {
178 let source = rfv.source().clone();
179 if let Some(spec_arc) = spec_arcs.remove(path) {
180 data.insert(
181 path.clone(),
182 DataDefinition::SpecRef {
183 spec: spec_arc,
184 source,
185 },
186 );
187 } else if let Some(pending) = references.remove(path) {
188 data.insert(
189 path.clone(),
190 DataDefinition::Reference {
191 target: pending.target,
192 resolved_type: pending.resolved_type,
193 local_constraints: pending.local_constraints,
194 local_default: pending.local_default,
195 source,
196 },
197 );
198 } else if let Some(value) = values.remove(path) {
199 data.insert(path.clone(), DataDefinition::Value { value, source });
200 } else {
201 let resolved_type = schema
202 .get(path)
203 .cloned()
204 .expect("non-spec-ref data has schema (value, reference, or type-only)");
205 let declared_default = declared_defaults.remove(path);
206 data.insert(
207 path.clone(),
208 DataDefinition::TypeDeclaration {
209 resolved_type,
210 declared_default,
211 source,
212 },
213 );
214 }
215 }
216 data
217 }
218
219 fn coerce_literal_to_schema_type(
220 lit: &LiteralValue,
221 schema_type: &LemmaType,
222 ) -> Result<LiteralValue, String> {
223 if lit.lemma_type.specifications == schema_type.specifications {
224 let mut out = lit.clone();
225 out.lemma_type = schema_type.clone();
226 return Ok(out);
227 }
228 match (&schema_type.specifications, &lit.value) {
229 (TypeSpecification::Number { .. }, ValueKind::Number(_))
230 | (TypeSpecification::Text { .. }, ValueKind::Text(_))
231 | (TypeSpecification::Boolean { .. }, ValueKind::Boolean(_))
232 | (TypeSpecification::Date { .. }, ValueKind::Date(_))
233 | (TypeSpecification::Time { .. }, ValueKind::Time(_))
234 | (TypeSpecification::Duration { .. }, ValueKind::Duration(_, _))
235 | (TypeSpecification::Ratio { .. }, ValueKind::Ratio(_, _))
236 | (TypeSpecification::Scale { .. }, ValueKind::Scale(_, _)) => {
237 let mut out = lit.clone();
238 out.lemma_type = schema_type.clone();
239 Ok(out)
240 }
241 (TypeSpecification::Ratio { .. }, ValueKind::Number(n)) => {
242 Ok(LiteralValue::ratio_with_type(*n, None, schema_type.clone()))
243 }
244 _ => Err(format!(
245 "value {} cannot be used as type {}",
246 lit,
247 schema_type.name()
248 )),
249 }
250 }
251
252 fn resolve_data_reference_types(&mut self) -> Result<(), Vec<Error>> {
264 let mut errors: Vec<Error> = Vec::new();
265 let mut updates: Vec<(DataPath, LemmaType, Option<ValueKind>)> = Vec::new();
266
267 for (reference_path, entry) in &self.data {
268 let DataDefinition::Reference {
269 target,
270 resolved_type: provisional,
271 local_constraints,
272 source,
273 ..
274 } = entry
275 else {
276 continue;
277 };
278
279 let target_data_path = match target {
280 ReferenceTarget::Data(path) => path,
281 ReferenceTarget::Rule(_) => continue,
282 };
283
284 let Some(target_entry) = self.data.get(target_data_path) else {
285 errors.push(reference_error(
286 &self.main_spec,
287 source,
288 format!(
289 "Data reference '{}' target '{}' does not exist",
290 reference_path, target_data_path
291 ),
292 ));
293 continue;
294 };
295
296 let Some(target_type) = target_entry.schema_type().cloned() else {
297 errors.push(reference_error(
298 &self.main_spec,
299 source,
300 format!(
301 "Data reference '{}' target '{}' is a spec reference and cannot carry a value",
302 reference_path, target_data_path
303 ),
304 ));
305 continue;
306 };
307
308 let lhs_declared_type: Option<&LemmaType> = if provisional.is_undetermined() {
309 None
310 } else {
311 Some(provisional)
312 };
313
314 if let Some(lhs) = lhs_declared_type {
315 if let Some(msg) = reference_kind_mismatch_message(
316 lhs,
317 &target_type,
318 reference_path,
319 target_data_path,
320 "target",
321 ) {
322 errors.push(reference_error(&self.main_spec, source, msg));
323 continue;
324 }
325 }
326
327 let mut merged = match lhs_declared_type {
332 Some(lhs) => lhs.clone(),
333 None => target_type.clone(),
334 };
335 let mut captured_default: Option<ValueKind> = None;
336 if let Some(constraints) = local_constraints {
337 match apply_constraints_to_spec(
338 &self.main_spec,
339 merged.specifications.clone(),
340 constraints,
341 source,
342 &mut captured_default,
343 ) {
344 Ok(specs) => merged.specifications = specs,
345 Err(errs) => {
346 errors.extend(errs);
347 continue;
348 }
349 }
350 }
351
352 updates.push((reference_path.clone(), merged, captured_default));
353 }
354
355 for (path, new_type, new_default) in updates {
356 if let Some(DataDefinition::Reference {
357 resolved_type,
358 local_default,
359 ..
360 }) = self.data.get_mut(&path)
361 {
362 *resolved_type = new_type;
363 if new_default.is_some() {
364 *local_default = new_default;
365 }
366 } else {
367 unreachable!("BUG: reference path disappeared between collect and update phases");
368 }
369 }
370
371 if errors.is_empty() {
372 Ok(())
373 } else {
374 Err(errors)
375 }
376 }
377
378 fn resolve_rule_reference_types(
387 &mut self,
388 computed_rule_types: &HashMap<RulePath, LemmaType>,
389 ) -> Result<(), Vec<Error>> {
390 let mut errors: Vec<Error> = Vec::new();
391 let mut updates: Vec<(DataPath, LemmaType, Option<ValueKind>)> = Vec::new();
392
393 for (reference_path, entry) in &self.data {
394 let DataDefinition::Reference {
395 target,
396 resolved_type: provisional,
397 local_constraints,
398 source,
399 ..
400 } = entry
401 else {
402 continue;
403 };
404
405 let target_rule_path = match target {
406 ReferenceTarget::Rule(path) => path,
407 ReferenceTarget::Data(_) => continue,
408 };
409
410 let Some(target_type) = computed_rule_types.get(target_rule_path) else {
411 errors.push(reference_error(
412 &self.main_spec,
413 source,
414 format!(
415 "Data reference '{}' target rule '{}' does not exist",
416 reference_path, target_rule_path
417 ),
418 ));
419 continue;
420 };
421
422 if target_type.vetoed() || target_type.is_undetermined() {
427 let mut merged = target_type.clone();
428 let mut captured_default: Option<ValueKind> = None;
429 if let Some(constraints) = local_constraints {
430 match apply_constraints_to_spec(
431 &self.main_spec,
432 merged.specifications.clone(),
433 constraints,
434 source,
435 &mut captured_default,
436 ) {
437 Ok(specs) => merged.specifications = specs,
438 Err(errs) => {
439 errors.extend(errs);
440 continue;
441 }
442 }
443 }
444 updates.push((reference_path.clone(), merged, captured_default));
445 continue;
446 }
447
448 let lhs_declared_type: Option<&LemmaType> = if provisional.is_undetermined() {
449 None
450 } else {
451 Some(provisional)
452 };
453
454 if let Some(lhs) = lhs_declared_type {
455 if let Some(msg) = reference_kind_mismatch_message(
456 lhs,
457 target_type,
458 reference_path,
459 target_rule_path,
460 "target rule",
461 ) {
462 errors.push(reference_error(&self.main_spec, source, msg));
463 continue;
464 }
465 }
466
467 let mut merged = match lhs_declared_type {
470 Some(lhs) => lhs.clone(),
471 None => target_type.clone(),
472 };
473 let mut captured_default: Option<ValueKind> = None;
474 if let Some(constraints) = local_constraints {
475 match apply_constraints_to_spec(
476 &self.main_spec,
477 merged.specifications.clone(),
478 constraints,
479 source,
480 &mut captured_default,
481 ) {
482 Ok(specs) => merged.specifications = specs,
483 Err(errs) => {
484 errors.extend(errs);
485 continue;
486 }
487 }
488 }
489
490 updates.push((reference_path.clone(), merged, captured_default));
491 }
492
493 for (path, new_type, new_default) in updates {
494 if let Some(DataDefinition::Reference {
495 resolved_type,
496 local_default,
497 ..
498 }) = self.data.get_mut(&path)
499 {
500 *resolved_type = new_type;
501 if new_default.is_some() {
502 *local_default = new_default;
503 }
504 } else {
505 unreachable!(
506 "BUG: rule-target reference path disappeared between collect and update phases"
507 );
508 }
509 }
510
511 if errors.is_empty() {
512 Ok(())
513 } else {
514 Err(errors)
515 }
516 }
517
518 fn add_rule_reference_dependency_edges(&mut self) {
528 let reference_to_rule: HashMap<DataPath, RulePath> =
529 self.transitive_reference_to_rule_map();
530
531 if reference_to_rule.is_empty() {
532 return;
533 }
534
535 let mut updates: Vec<(RulePath, RulePath)> = Vec::new();
536 for (rule_path, rule_node) in &self.rules {
537 let mut found: BTreeSet<RulePath> = BTreeSet::new();
538 for (cond, result) in &rule_node.branches {
539 if let Some(c) = cond {
540 collect_rule_reference_dependencies(c, &reference_to_rule, &mut found);
541 }
542 collect_rule_reference_dependencies(result, &reference_to_rule, &mut found);
543 }
544 for target in found {
545 updates.push((rule_path.clone(), target));
546 }
547 }
548
549 for (rule_path, target) in updates {
550 if let Some(node) = self.rules.get_mut(&rule_path) {
551 node.depends_on_rules.insert(target);
552 }
553 }
554 }
555
556 fn transitive_reference_to_rule_map(&self) -> HashMap<DataPath, RulePath> {
563 let mut out: HashMap<DataPath, RulePath> = HashMap::new();
564 for (path, def) in &self.data {
565 if !matches!(def, DataDefinition::Reference { .. }) {
566 continue;
567 }
568 let mut visited: HashSet<DataPath> = HashSet::new();
569 let mut cursor: DataPath = path.clone();
570 loop {
571 if !visited.insert(cursor.clone()) {
572 break;
573 }
574 let Some(DataDefinition::Reference { target, .. }) = self.data.get(&cursor) else {
575 break;
576 };
577 match target {
578 ReferenceTarget::Data(next) => cursor = next.clone(),
579 ReferenceTarget::Rule(rule_path) => {
580 out.insert(path.clone(), rule_path.clone());
581 break;
582 }
583 }
584 }
585 }
586 out
587 }
588
589 fn compute_reference_evaluation_order(&self) -> Result<Vec<DataPath>, Vec<Error>> {
596 let reference_paths: Vec<DataPath> = self
597 .data
598 .iter()
599 .filter_map(|(p, d)| match d {
600 DataDefinition::Reference {
601 target: ReferenceTarget::Data(_),
602 ..
603 } => Some(p.clone()),
604 _ => None,
605 })
606 .collect();
607
608 if reference_paths.is_empty() {
609 return Ok(Vec::new());
610 }
611
612 let reference_set: BTreeSet<DataPath> = reference_paths.iter().cloned().collect();
613 let mut in_degree: BTreeMap<DataPath, usize> = BTreeMap::new();
614 let mut dependents: BTreeMap<DataPath, Vec<DataPath>> = BTreeMap::new();
615 for p in &reference_paths {
616 in_degree.insert(p.clone(), 0);
617 dependents.insert(p.clone(), Vec::new());
618 }
619
620 for p in &reference_paths {
621 let Some(DataDefinition::Reference { target, .. }) = self.data.get(p) else {
622 unreachable!("BUG: reference entry lost between collect and walk");
623 };
624 if let ReferenceTarget::Data(target_path) = target {
625 if reference_set.contains(target_path) {
626 *in_degree
627 .get_mut(p)
628 .expect("BUG: reference missing in_degree") += 1;
629 dependents
630 .get_mut(target_path)
631 .expect("BUG: reference missing dependents list")
632 .push(p.clone());
633 }
634 }
635 }
636
637 let mut queue: VecDeque<DataPath> = in_degree
638 .iter()
639 .filter(|(_, d)| **d == 0)
640 .map(|(p, _)| p.clone())
641 .collect();
642
643 let mut result: Vec<DataPath> = Vec::new();
644 while let Some(path) = queue.pop_front() {
645 result.push(path.clone());
646 if let Some(deps) = dependents.get(&path) {
647 for dependent in deps.clone() {
648 let degree = in_degree
649 .get_mut(&dependent)
650 .expect("BUG: reference dependent missing in_degree");
651 *degree -= 1;
652 if *degree == 0 {
653 queue.push_back(dependent);
654 }
655 }
656 }
657 }
658
659 if result.len() != reference_paths.len() {
660 let cycle_members: Vec<DataPath> = reference_paths
661 .iter()
662 .filter(|p| !result.contains(p))
663 .cloned()
664 .collect();
665 let cycle_display: String = cycle_members
666 .iter()
667 .map(|p| p.to_string())
668 .collect::<Vec<_>>()
669 .join(", ");
670 let errors: Vec<Error> = cycle_members
671 .iter()
672 .filter_map(|p| {
673 self.data.get(p).map(|entry| {
674 reference_error(
675 &self.main_spec,
676 entry.source(),
677 format!("Circular data reference ({})", cycle_display),
678 )
679 })
680 })
681 .collect();
682 return Err(errors);
683 }
684
685 Ok(result)
686 }
687
688 fn topological_sort(&self) -> Result<Vec<RulePath>, Vec<Error>> {
689 let mut in_degree: BTreeMap<RulePath, usize> = BTreeMap::new();
690 let mut dependents: BTreeMap<RulePath, Vec<RulePath>> = BTreeMap::new();
691 let mut queue = VecDeque::new();
692 let mut result = Vec::new();
693
694 for rule_path in self.rules.keys() {
695 in_degree.insert(rule_path.clone(), 0);
696 dependents.insert(rule_path.clone(), Vec::new());
697 }
698
699 for (rule_path, rule_node) in &self.rules {
700 for dependency in &rule_node.depends_on_rules {
701 if self.rules.contains_key(dependency) {
702 if let Some(degree) = in_degree.get_mut(rule_path) {
703 *degree += 1;
704 }
705 if let Some(deps) = dependents.get_mut(dependency) {
706 deps.push(rule_path.clone());
707 }
708 }
709 }
710 }
711
712 for (rule_path, degree) in &in_degree {
713 if *degree == 0 {
714 queue.push_back(rule_path.clone());
715 }
716 }
717
718 while let Some(rule_path) = queue.pop_front() {
719 result.push(rule_path.clone());
720
721 if let Some(dependent_rules) = dependents.get(&rule_path) {
722 for dependent in dependent_rules {
723 if let Some(degree) = in_degree.get_mut(dependent) {
724 *degree -= 1;
725 if *degree == 0 {
726 queue.push_back(dependent.clone());
727 }
728 }
729 }
730 }
731 }
732
733 if result.len() != self.rules.len() {
734 let missing: Vec<RulePath> = self
735 .rules
736 .keys()
737 .filter(|rule| !result.contains(rule))
738 .cloned()
739 .collect();
740 let cycle: Vec<Source> = missing
741 .iter()
742 .filter_map(|rule| self.rules.get(rule).map(|n| n.source.clone()))
743 .collect();
744
745 if cycle.is_empty() {
746 unreachable!(
747 "BUG: circular dependency detected but no sources could be collected ({} missing rules)",
748 missing.len()
749 );
750 }
751 let rules_involved: String = missing
752 .iter()
753 .map(|rp| rp.rule.as_str())
754 .collect::<Vec<_>>()
755 .join(", ");
756 let message = format!("Circular dependency (rules: {})", rules_involved);
757 let errors: Vec<Error> = cycle
758 .into_iter()
759 .map(|source| {
760 Error::validation_with_context(
761 message.clone(),
762 Some(source),
763 None::<String>,
764 Some(Arc::clone(&self.main_spec)),
765 None,
766 )
767 })
768 .collect();
769 return Err(errors);
770 }
771
772 Ok(result)
773 }
774}
775
776#[derive(Debug)]
777pub(crate) struct RuleNode {
778 pub branches: Vec<(Option<Expression>, Expression)>,
781 pub source: Source,
782
783 pub depends_on_rules: BTreeSet<RulePath>,
784
785 pub rule_type: LemmaType,
788
789 pub spec_name: String,
791}
792
793type ResolvedTypesMap = HashMap<Arc<LemmaSpec>, ResolvedSpecTypes>;
794
795struct GraphBuilder<'a> {
796 data: IndexMap<DataPath, DataDefinition>,
797 rules: BTreeMap<RulePath, RuleNode>,
798 context: &'a Context,
799 local_types: ResolvedTypesMap,
800 errors: Vec<Error>,
801 main_spec: Arc<LemmaSpec>,
802}
803
804fn reference_error(main_spec: &Arc<LemmaSpec>, source: &Source, message: String) -> Error {
805 Error::validation_with_context(
806 message,
807 Some(source.clone()),
808 None::<String>,
809 Some(Arc::clone(main_spec)),
810 None,
811 )
812}
813
814fn reference_kind_mismatch_message<P: fmt::Display>(
831 lhs: &LemmaType,
832 target_type: &LemmaType,
833 reference_path: &DataPath,
834 target_path: &P,
835 target_kind_label: &str,
836) -> Option<String> {
837 if !lhs.has_same_base_type(target_type) {
838 return Some(format!(
839 "Data reference '{}' type mismatch: declared as '{}' but {} '{}' is '{}'",
840 reference_path,
841 lhs.name(),
842 target_kind_label,
843 target_path,
844 target_type.name(),
845 ));
846 }
847 if lhs.is_scale() && !lhs.same_scale_family(target_type) {
848 let lhs_family = lhs.scale_family_name().expect(
849 "BUG: declared scale data must carry a family name; \
850 anonymous scale types only arise from runtime synthesis \
851 and never appear as a reference's LHS-declared type",
852 );
853 let target_family = target_type.scale_family_name().expect(
854 "BUG: declared scale data must carry a family name; \
855 anonymous scale types only arise from runtime synthesis \
856 and never appear as a reference target's schema type",
857 );
858 return Some(format!(
859 "Data reference '{}' scale family mismatch: declared as '{}' (family '{}') but {} '{}' is '{}' (family '{}')",
860 reference_path,
861 lhs.name(),
862 lhs_family,
863 target_kind_label,
864 target_path,
865 target_type.name(),
866 target_family,
867 ));
868 }
869 None
870}
871
872fn apply_constraints_to_spec(
877 spec: &Arc<LemmaSpec>,
878 mut specs: TypeSpecification,
879 constraints: &[Constraint],
880 source: &crate::Source,
881 declared_default: &mut Option<ValueKind>,
882) -> Result<TypeSpecification, Vec<Error>> {
883 let mut errors = Vec::new();
884 for (command, args) in constraints {
885 let specs_clone = specs.clone();
886 let mut default_before = declared_default.clone();
887 match specs.apply_constraint(*command, args, &mut default_before) {
888 Ok(updated_specs) => {
889 specs = updated_specs;
890 *declared_default = default_before;
891 }
892 Err(e) => {
893 errors.push(Error::validation_with_context(
894 format!("Failed to apply constraint '{}': {}", command, e),
895 Some(source.clone()),
896 None::<String>,
897 Some(Arc::clone(spec)),
898 None,
899 ));
900 specs = specs_clone;
901 }
902 }
903 }
904 if !errors.is_empty() {
905 return Err(errors);
906 }
907 Ok(specs)
908}
909
910impl Graph {
911 pub(crate) fn build(
913 context: &Context,
914 main_spec: &Arc<LemmaSpec>,
915 dag: &[Arc<LemmaSpec>],
916 effective: &EffectiveDate,
917 ) -> Result<(Graph, ResolvedTypesMap), Vec<Error>> {
918 let mut type_resolver = TypeResolver::new(context, dag);
919
920 let mut type_errors: Vec<Error> = Vec::new();
921 for spec in dag {
922 type_errors.extend(type_resolver.register_all(spec));
923 }
924
925 let (data, rules, graph_errors, local_types) = {
926 let mut builder = GraphBuilder {
927 data: IndexMap::new(),
928 rules: BTreeMap::new(),
929 context,
930 local_types: HashMap::new(),
931 errors: Vec::new(),
932 main_spec: Arc::clone(main_spec),
933 };
934
935 builder.build_spec(
936 main_spec,
937 Vec::new(),
938 HashMap::new(),
939 effective,
940 &mut type_resolver,
941 )?;
942
943 (
944 builder.data,
945 builder.rules,
946 builder.errors,
947 builder.local_types,
948 )
949 };
950
951 let mut graph = Graph {
952 data,
953 rules,
954 execution_order: Vec::new(),
955 reference_evaluation_order: Vec::new(),
956 main_spec: Arc::clone(main_spec),
957 };
958
959 let validation_errors = match graph.validate(&local_types) {
960 Ok(()) => Vec::new(),
961 Err(errors) => errors,
962 };
963
964 let mut all_errors = type_errors;
965 all_errors.extend(graph_errors);
966 all_errors.extend(validation_errors);
967
968 if all_errors.is_empty() {
969 Ok((graph, local_types))
970 } else {
971 Err(all_errors)
972 }
973 }
974
975 fn validate(&mut self, resolved_types: &ResolvedTypesMap) -> Result<(), Vec<Error>> {
976 let mut errors = Vec::new();
977
978 if let Err(structural_errors) = check_all_rule_references_exist(self) {
980 errors.extend(structural_errors);
981 }
982 if let Err(collision_errors) = check_data_and_rule_name_collisions(self) {
983 errors.extend(collision_errors);
984 }
985
986 if let Err(reference_errors) = self.resolve_data_reference_types() {
990 errors.extend(reference_errors);
991 }
992
993 let reference_order = match self.compute_reference_evaluation_order() {
997 Ok(order) => order,
998 Err(circular_errors) => {
999 errors.extend(circular_errors);
1000 return Err(errors);
1001 }
1002 };
1003
1004 self.add_rule_reference_dependency_edges();
1010
1011 let execution_order = match self.topological_sort() {
1012 Ok(order) => order,
1013 Err(circular_errors) => {
1014 errors.extend(circular_errors);
1015 return Err(errors);
1016 }
1017 };
1018
1019 let inferred_types = infer_rule_types(self, &execution_order, resolved_types);
1027
1028 if let Err(rule_reference_errors) = self.resolve_rule_reference_types(&inferred_types) {
1033 errors.extend(rule_reference_errors);
1034 }
1035
1036 if let Err(type_errors) =
1038 check_rule_types(self, &execution_order, &inferred_types, resolved_types)
1039 {
1040 errors.extend(type_errors);
1041 }
1042
1043 if !errors.is_empty() {
1044 return Err(errors);
1045 }
1046
1047 apply_inferred_types(self, inferred_types);
1049 self.execution_order = execution_order;
1050 self.reference_evaluation_order = reference_order;
1051 Ok(())
1052 }
1053}
1054
1055impl<'a> GraphBuilder<'a> {
1056 fn engine_error(&self, message: impl Into<String>, source: &Source) -> Error {
1057 Error::validation_with_context(
1058 message.into(),
1059 Some(source.clone()),
1060 None::<String>,
1061 Some(Arc::clone(&self.main_spec)),
1062 None,
1063 )
1064 }
1065
1066 fn process_meta_fields(&mut self, spec: &LemmaSpec) {
1067 let mut seen = HashSet::new();
1068 for field in &spec.meta_fields {
1069 if field.key == "title" && !matches!(field.value, MetaValue::Literal(Value::Text(_))) {
1071 self.errors.push(self.engine_error(
1072 "Meta 'title' must be a text literal",
1073 &field.source_location,
1074 ));
1075 }
1076
1077 if !seen.insert(field.key.clone()) {
1078 self.errors.push(self.engine_error(
1079 format!("Duplicate meta key '{}'", field.key),
1080 &field.source_location,
1081 ));
1082 }
1083 }
1084 }
1085
1086 fn resolve_spec_ref(
1087 &self,
1088 spec_ref: &ast::SpecRef,
1089 effective: &EffectiveDate,
1090 ) -> Result<Arc<LemmaSpec>, Error> {
1091 discovery::resolve_spec_ref(
1092 self.context,
1093 spec_ref,
1094 effective,
1095 &self.main_spec.name,
1096 None,
1097 Some(Arc::clone(&self.main_spec)),
1098 )
1099 }
1100
1101 fn resolve_data_binding(
1108 &mut self,
1109 data: &LemmaData,
1110 current_segment_names: &[String],
1111 parent_spec: &Arc<LemmaSpec>,
1112 effective: &EffectiveDate,
1113 ) -> Option<(Vec<String>, BindingValue, Source)> {
1114 let binding_path_display = format!(
1115 "{}.{}",
1116 data.reference.segments.join("."),
1117 data.reference.name
1118 );
1119
1120 let mut walk_spec = Arc::clone(parent_spec);
1121
1122 for segment in &data.reference.segments {
1123 let Some(seg_data) = walk_spec
1124 .data
1125 .iter()
1126 .find(|f| f.reference.segments.is_empty() && f.reference.name == *segment)
1127 else {
1128 self.errors.push(self.engine_error(
1129 format!(
1130 "Data binding path '{}': data '{}' not found in spec '{}'",
1131 binding_path_display, segment, walk_spec.name
1132 ),
1133 &data.source_location,
1134 ));
1135 return None;
1136 };
1137
1138 let spec_ref = match &seg_data.value {
1139 ParsedDataValue::SpecReference(sr) => sr,
1140 _ => {
1141 self.errors.push(self.engine_error(
1142 format!(
1143 "Data binding path '{}': '{}' in spec '{}' is not a spec reference",
1144 binding_path_display, segment, walk_spec.name
1145 ),
1146 &data.source_location,
1147 ));
1148 return None;
1149 }
1150 };
1151
1152 walk_spec = match self.resolve_spec_ref(spec_ref, effective) {
1153 Ok(arc) => arc,
1154 Err(e) => {
1155 self.errors.push(e);
1156 return None;
1157 }
1158 };
1159 }
1160
1161 let mut binding_key: Vec<String> = current_segment_names.to_vec();
1163 binding_key.extend(data.reference.segments.iter().cloned());
1164 binding_key.push(data.reference.name.clone());
1165
1166 let binding_value = match &data.value {
1167 ParsedDataValue::Literal(v) => BindingValue::Literal(v.clone()),
1168 ParsedDataValue::Reference {
1169 target,
1170 constraints,
1171 } => {
1172 let resolved_target = self.resolve_reference_target_in_spec(
1173 target,
1174 &data.source_location,
1175 parent_spec,
1176 current_segment_names,
1177 effective,
1178 )?;
1179 BindingValue::Reference {
1180 target: resolved_target,
1181 constraints: constraints.clone(),
1182 }
1183 }
1184 ParsedDataValue::TypeDeclaration { .. } | ParsedDataValue::SpecReference(_) => {
1185 unreachable!(
1186 "BUG: build_data_bindings must reject TypeDeclaration/SpecReference bindings before calling resolve_data_binding"
1187 );
1188 }
1189 };
1190
1191 Some((binding_key, binding_value, data.source_location.clone()))
1192 }
1193
1194 fn resolve_reference_target_in_spec(
1201 &mut self,
1202 reference: &ast::Reference,
1203 reference_source: &Source,
1204 containing_spec_arc: &Arc<LemmaSpec>,
1205 containing_segments_names: &[String],
1206 effective: &EffectiveDate,
1207 ) -> Option<ReferenceTarget> {
1208 let containing_data_map: HashMap<String, LemmaData> = containing_spec_arc
1209 .data
1210 .iter()
1211 .filter(|d| d.reference.is_local())
1212 .map(|d| (d.reference.name.clone(), d.clone()))
1213 .collect();
1214
1215 let containing_rule_names: HashSet<&str> = containing_spec_arc
1216 .rules
1217 .iter()
1218 .map(|r| r.name.as_str())
1219 .collect();
1220
1221 let containing_segments: Vec<PathSegment> = containing_segments_names
1222 .iter()
1223 .map(|name| PathSegment {
1224 data: name.clone(),
1225 spec: containing_spec_arc.name.clone(),
1226 })
1227 .collect();
1228
1229 if reference.segments.is_empty() {
1230 let is_data = containing_data_map.contains_key(&reference.name);
1231 let is_rule = containing_rule_names.contains(reference.name.as_str());
1232 if is_data && is_rule {
1233 self.errors.push(self.engine_error(
1234 format!(
1235 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
1236 reference.name, containing_spec_arc.name
1237 ),
1238 reference_source,
1239 ));
1240 return None;
1241 }
1242 if is_data {
1243 return Some(ReferenceTarget::Data(DataPath {
1244 segments: containing_segments,
1245 data: reference.name.clone(),
1246 }));
1247 }
1248 if is_rule {
1249 return Some(ReferenceTarget::Rule(RulePath {
1250 segments: containing_segments,
1251 rule: reference.name.clone(),
1252 }));
1253 }
1254 self.errors.push(self.engine_error(
1255 format!(
1256 "Reference target '{}' not found in spec '{}'",
1257 reference.name, containing_spec_arc.name
1258 ),
1259 reference_source,
1260 ));
1261 return None;
1262 }
1263
1264 let (resolved_segments, target_spec_arc) = self.resolve_path_segments(
1265 &reference.segments,
1266 reference_source,
1267 containing_data_map,
1268 containing_segments,
1269 effective,
1270 )?;
1271
1272 let target_data_names: HashSet<&str> = target_spec_arc
1273 .data
1274 .iter()
1275 .filter(|d| d.reference.is_local())
1276 .map(|d| d.reference.name.as_str())
1277 .collect();
1278 let target_rule_names: HashSet<&str> = target_spec_arc
1279 .rules
1280 .iter()
1281 .map(|r| r.name.as_str())
1282 .collect();
1283 let is_data = target_data_names.contains(reference.name.as_str());
1284 let is_rule = target_rule_names.contains(reference.name.as_str());
1285
1286 if is_data && is_rule {
1287 self.errors.push(self.engine_error(
1288 format!(
1289 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
1290 reference.name, target_spec_arc.name
1291 ),
1292 reference_source,
1293 ));
1294 return None;
1295 }
1296 if is_data {
1297 return Some(ReferenceTarget::Data(DataPath {
1298 segments: resolved_segments,
1299 data: reference.name.clone(),
1300 }));
1301 }
1302 if is_rule {
1303 return Some(ReferenceTarget::Rule(RulePath {
1304 segments: resolved_segments,
1305 rule: reference.name.clone(),
1306 }));
1307 }
1308
1309 self.errors.push(self.engine_error(
1310 format!(
1311 "Reference target '{}' not found in spec '{}'",
1312 reference.name, target_spec_arc.name
1313 ),
1314 reference_source,
1315 ));
1316 None
1317 }
1318
1319 fn build_data_bindings(
1325 &mut self,
1326 spec: &LemmaSpec,
1327 current_segment_names: &[String],
1328 spec_arc: &Arc<LemmaSpec>,
1329 effective: &EffectiveDate,
1330 ) -> Result<DataBindings, Vec<Error>> {
1331 let mut bindings: DataBindings = HashMap::new();
1332 let mut errors: Vec<Error> = Vec::new();
1333
1334 for data in &spec.data {
1335 if data.reference.segments.is_empty() {
1336 continue; }
1338
1339 let binding_path_display = format!(
1340 "{}.{}",
1341 data.reference.segments.join("."),
1342 data.reference.name
1343 );
1344
1345 if matches!(&data.value, ParsedDataValue::SpecReference { .. }) {
1347 errors.push(self.engine_error(
1348 format!(
1349 "Data binding '{}' cannot override a spec reference — only literal values can be bound to nested data",
1350 binding_path_display
1351 ),
1352 &data.source_location,
1353 ));
1354 continue;
1355 }
1356
1357 if matches!(&data.value, ParsedDataValue::TypeDeclaration { .. }) {
1359 errors.push(self.engine_error(
1360 format!(
1361 "Data binding '{}' must provide a literal value, not a type declaration",
1362 binding_path_display
1363 ),
1364 &data.source_location,
1365 ));
1366 continue;
1367 }
1368
1369 if let Some((binding_key, binding_value, source)) =
1370 self.resolve_data_binding(data, current_segment_names, spec_arc, effective)
1371 {
1372 if let Some((_, existing_source)) = bindings.get(&binding_key) {
1373 errors.push(self.engine_error(
1374 format!(
1375 "Duplicate data binding for '{}' (previously bound at {}:{})",
1376 binding_key.join("."),
1377 existing_source.attribute,
1378 existing_source.span.line
1379 ),
1380 &data.source_location,
1381 ));
1382 } else {
1383 bindings.insert(binding_key, (binding_value, source));
1384 }
1385 }
1386 }
1388
1389 if !errors.is_empty() {
1390 return Err(errors);
1391 }
1392
1393 Ok(bindings)
1394 }
1395
1396 #[allow(clippy::too_many_arguments)]
1402 fn add_data(
1403 &mut self,
1404 data: &LemmaData,
1405 current_segments: &[PathSegment],
1406 data_bindings: &DataBindings,
1407 current_spec_arc: &Arc<LemmaSpec>,
1408 used_binding_keys: &mut HashSet<Vec<String>>,
1409 effective: &EffectiveDate,
1410 ) {
1411 let data_path = DataPath {
1412 segments: current_segments.to_vec(),
1413 data: data.reference.name.clone(),
1414 };
1415
1416 if self.data.contains_key(&data_path) {
1418 self.errors.push(self.engine_error(
1419 format!("Duplicate data '{}'", data_path.data),
1420 &data.source_location,
1421 ));
1422 return;
1423 }
1424
1425 let binding_key: Vec<String> = current_segments
1427 .iter()
1428 .map(|s| s.data.clone())
1429 .chain(std::iter::once(data.reference.name.clone()))
1430 .collect();
1431
1432 let binding_override: Option<(BindingValue, Source)> =
1436 data_bindings.get(&binding_key).map(|(v, s)| {
1437 used_binding_keys.insert(binding_key.clone());
1438 (v.clone(), s.clone())
1439 });
1440
1441 let (original_schema_type, original_declared_default) =
1442 if matches!(&data.value, ParsedDataValue::TypeDeclaration { .. }) {
1443 let resolved = self
1444 .local_types
1445 .get(current_spec_arc)
1446 .expect("BUG: no resolved types for spec during add_local_data");
1447 let lemma_type = resolved
1448 .named_types
1449 .get(&data.reference.name)
1450 .expect("BUG: type not in named_types — TypeResolver should have registered it")
1451 .clone();
1452 let declared = resolved
1453 .declared_defaults
1454 .get(&data.reference.name)
1455 .cloned();
1456 (Some(lemma_type), declared)
1457 } else {
1458 (None, None)
1459 };
1460
1461 if let Some((binding_value, binding_source)) = binding_override {
1462 self.add_data_from_binding(
1463 data_path,
1464 binding_value,
1465 binding_source,
1466 original_schema_type,
1467 current_spec_arc,
1468 );
1469 return;
1470 }
1471
1472 let effective_source = data.source_location.clone();
1473
1474 match &data.value {
1475 ParsedDataValue::Literal(value) => {
1476 self.insert_literal_data(
1477 data_path,
1478 value,
1479 original_schema_type,
1480 effective_source,
1481 current_spec_arc,
1482 );
1483 }
1484 ParsedDataValue::TypeDeclaration { .. } => {
1485 let resolved_type = original_schema_type.unwrap_or_else(|| {
1486 unreachable!(
1487 "BUG: TypeDeclaration effective value without original_schema_type"
1488 )
1489 });
1490
1491 self.data.insert(
1492 data_path,
1493 DataDefinition::TypeDeclaration {
1494 resolved_type,
1495 declared_default: original_declared_default,
1496 source: effective_source,
1497 },
1498 );
1499 }
1500 ParsedDataValue::SpecReference(spec_ref) => {
1501 let effective_spec_arc = match self.resolve_spec_ref(spec_ref, effective) {
1502 Ok(arc) => arc,
1503 Err(e) => {
1504 self.errors.push(e);
1505 return;
1506 }
1507 };
1508
1509 self.data.insert(
1510 data_path,
1511 DataDefinition::SpecRef {
1512 spec: Arc::clone(&effective_spec_arc),
1513 source: effective_source,
1514 },
1515 );
1516 }
1517 ParsedDataValue::Reference {
1518 target,
1519 constraints,
1520 } => {
1521 let current_segment_names: Vec<String> =
1522 current_segments.iter().map(|s| s.data.clone()).collect();
1523 let Some(resolved_target) = self.resolve_reference_target_in_spec(
1524 target,
1525 &effective_source,
1526 current_spec_arc,
1527 ¤t_segment_names,
1528 effective,
1529 ) else {
1530 return;
1531 };
1532 let provisional_type = original_schema_type
1536 .clone()
1537 .unwrap_or_else(LemmaType::undetermined_type);
1538 self.data.insert(
1539 data_path,
1540 DataDefinition::Reference {
1541 target: resolved_target,
1542 resolved_type: provisional_type,
1543 local_constraints: constraints.clone(),
1544 local_default: None,
1545 source: effective_source,
1546 },
1547 );
1548 }
1549 }
1550 }
1551
1552 fn insert_literal_data(
1556 &mut self,
1557 data_path: DataPath,
1558 value: &ast::Value,
1559 declared_schema_type: Option<LemmaType>,
1560 effective_source: Source,
1561 current_spec_arc: &Arc<LemmaSpec>,
1562 ) {
1563 let semantic_value = match value_to_semantic(value) {
1564 Ok(s) => s,
1565 Err(e) => {
1566 self.errors.push(self.engine_error(e, &effective_source));
1567 return;
1568 }
1569 };
1570 let inferred_type = match value {
1571 Value::Text(_) => primitive_text().clone(),
1572 Value::Number(_) => primitive_number().clone(),
1573 Value::Scale(_, unit) => {
1574 match self
1575 .local_types
1576 .get(current_spec_arc)
1577 .and_then(|dt| dt.unit_index.get(unit))
1578 {
1579 Some(lt) => lt.clone(),
1580 None => {
1581 self.errors.push(self.engine_error(
1582 format!("Scale literal uses unknown unit '{}' for this spec", unit),
1583 &effective_source,
1584 ));
1585 return;
1586 }
1587 }
1588 }
1589 Value::Boolean(_) => primitive_boolean().clone(),
1590 Value::Date(_) => primitive_date().clone(),
1591 Value::Time(_) => primitive_time().clone(),
1592 Value::Duration(_, _) => primitive_duration().clone(),
1593 Value::Ratio(_, _) => primitive_ratio().clone(),
1594 };
1595 let schema_type = declared_schema_type.unwrap_or(inferred_type);
1596 let literal_value = LiteralValue {
1597 value: semantic_value,
1598 lemma_type: schema_type,
1599 };
1600 self.data.insert(
1601 data_path,
1602 DataDefinition::Value {
1603 value: literal_value,
1604 source: effective_source,
1605 },
1606 );
1607 }
1608
1609 fn add_data_from_binding(
1612 &mut self,
1613 data_path: DataPath,
1614 binding_value: BindingValue,
1615 binding_source: Source,
1616 declared_schema_type: Option<LemmaType>,
1617 current_spec_arc: &Arc<LemmaSpec>,
1618 ) {
1619 match binding_value {
1620 BindingValue::Literal(value) => {
1621 self.insert_literal_data(
1622 data_path,
1623 &value,
1624 declared_schema_type,
1625 binding_source,
1626 current_spec_arc,
1627 );
1628 }
1629 BindingValue::Reference {
1630 target,
1631 constraints,
1632 } => {
1633 let provisional_type =
1634 declared_schema_type.unwrap_or_else(LemmaType::undetermined_type);
1635 self.data.insert(
1636 data_path,
1637 DataDefinition::Reference {
1638 target,
1639 resolved_type: provisional_type,
1640 local_constraints: constraints,
1641 local_default: None,
1642 source: binding_source,
1643 },
1644 );
1645 }
1646 }
1647 }
1648
1649 fn resolve_path_segments(
1651 &mut self,
1652 segments: &[String],
1653 reference_source: &Source,
1654 mut current_data_map: HashMap<String, LemmaData>,
1655 mut path_segments: Vec<PathSegment>,
1656 effective: &EffectiveDate,
1657 ) -> Option<(Vec<PathSegment>, Arc<LemmaSpec>)> {
1658 let mut last_arc: Option<Arc<LemmaSpec>> = None;
1659
1660 for segment in segments.iter() {
1661 let data_ref =
1662 match current_data_map.get(segment) {
1663 Some(f) => f,
1664 None => {
1665 self.errors.push(self.engine_error(
1666 format!("Data '{}' not found", segment),
1667 reference_source,
1668 ));
1669 return None;
1670 }
1671 };
1672
1673 if let ParsedDataValue::SpecReference(original_spec_ref) = &data_ref.value {
1674 let arc = match self.resolve_spec_ref(original_spec_ref, effective) {
1675 Ok(a) => a,
1676 Err(e) => {
1677 self.errors.push(e);
1678 return None;
1679 }
1680 };
1681
1682 path_segments.push(PathSegment {
1683 data: segment.clone(),
1684 spec: arc.name.clone(),
1685 });
1686 current_data_map = arc
1687 .data
1688 .iter()
1689 .map(|f| (f.reference.name.clone(), f.clone()))
1690 .collect();
1691 last_arc = Some(arc);
1692 } else {
1693 self.errors.push(self.engine_error(
1694 format!("Data '{}' is not a spec reference", segment),
1695 reference_source,
1696 ));
1697 return None;
1698 }
1699 }
1700
1701 let final_arc = last_arc.unwrap_or_else(|| {
1702 unreachable!(
1703 "BUG: resolve_path_segments called with empty segments should not reach here"
1704 )
1705 });
1706 Some((path_segments, final_arc))
1707 }
1708
1709 fn build_spec(
1710 &mut self,
1711 spec_arc: &Arc<LemmaSpec>,
1712 current_segments: Vec<PathSegment>,
1713 data_bindings: DataBindings,
1714 effective: &EffectiveDate,
1715 type_resolver: &mut TypeResolver<'a>,
1716 ) -> Result<(), Vec<Error>> {
1717 let spec = spec_arc.as_ref();
1718
1719 if current_segments.is_empty() {
1720 self.process_meta_fields(spec);
1721 }
1722
1723 for data in spec.data.iter() {
1726 if let ParsedDataValue::SpecReference(spec_ref) = &data.value {
1727 if spec_ref.name == spec.name {
1728 self.errors.push(self.engine_error(
1729 format!(
1730 "spec '{}' cannot reference '{}' (same base name)",
1731 spec.name, spec_ref
1732 ),
1733 &data.source_location,
1734 ));
1735 }
1736 }
1737 }
1738 let current_segment_names: Vec<String> =
1739 current_segments.iter().map(|s| s.data.clone()).collect();
1740
1741 let this_spec_bindings =
1743 match self.build_data_bindings(spec, ¤t_segment_names, spec_arc, effective) {
1744 Ok(bindings) => bindings,
1745 Err(errors) => {
1746 self.errors.extend(errors);
1747 HashMap::new()
1748 }
1749 };
1750
1751 let data_map: HashMap<String, &LemmaData> = spec
1753 .data
1754 .iter()
1755 .map(|data| (data.reference.name.clone(), data))
1756 .collect();
1757
1758 if !self.local_types.contains_key(spec_arc) {
1759 match type_resolver.resolve_and_validate(spec_arc, effective) {
1760 Ok(resolved_types) => {
1761 self.local_types
1762 .insert(Arc::clone(spec_arc), resolved_types);
1763 }
1764 Err(es) => {
1765 self.errors.extend(es);
1766 return Ok(());
1767 }
1768 }
1769 }
1770
1771 for data in &spec.data {
1772 if let ParsedDataValue::TypeDeclaration {
1773 from: Some(from_ref),
1774 ..
1775 } = &data.value
1776 {
1777 match self.resolve_spec_ref(from_ref, effective) {
1778 Ok(source_arc) => {
1779 if let std::collections::hash_map::Entry::Vacant(e) =
1780 self.local_types.entry(source_arc)
1781 {
1782 match type_resolver.resolve_and_validate(e.key(), effective) {
1783 Ok(resolved_types) => {
1784 e.insert(resolved_types);
1785 }
1786 Err(es) => self.errors.extend(es),
1787 }
1788 }
1789 }
1790 Err(e) => self.errors.push(e),
1791 }
1792 }
1793 }
1794
1795 let mut used_binding_keys: HashSet<Vec<String>> = HashSet::new();
1797 for data in &spec.data {
1798 if !data.reference.segments.is_empty() {
1799 continue; }
1801 if let ParsedDataValue::SpecReference(spec_ref) = &data.value {
1802 if spec_ref.name == spec.name {
1803 continue; }
1805 }
1806 self.add_data(
1807 data,
1808 ¤t_segments,
1809 &data_bindings,
1810 spec_arc,
1811 &mut used_binding_keys,
1812 effective,
1813 );
1814 }
1815
1816 for data in &spec.data {
1817 if !data.reference.segments.is_empty() {
1818 continue;
1819 }
1820 if let ParsedDataValue::SpecReference(spec_ref) = &data.value {
1821 if spec_ref.name == spec.name {
1822 continue; }
1824 let nested_effective = spec_ref.at(effective);
1825 let nested_arc = match self.resolve_spec_ref(spec_ref, effective) {
1826 Ok(arc) => arc,
1827 Err(e) => {
1828 self.errors.push(e);
1829 continue;
1830 }
1831 };
1832 let mut nested_segments = current_segments.clone();
1833 nested_segments.push(PathSegment {
1834 data: data.reference.name.clone(),
1835 spec: nested_arc.name.clone(),
1836 });
1837
1838 let nested_segment_names: Vec<String> =
1839 nested_segments.iter().map(|s| s.data.clone()).collect();
1840 let mut combined_bindings = this_spec_bindings.clone();
1841 for (key, value_and_source) in &data_bindings {
1842 if key.len() > nested_segment_names.len()
1843 && key[..nested_segment_names.len()] == nested_segment_names[..]
1844 && !combined_bindings.contains_key(key)
1845 {
1846 combined_bindings.insert(key.clone(), value_and_source.clone());
1847 }
1848 }
1849
1850 if let Err(errs) = self.build_spec(
1851 &nested_arc,
1852 nested_segments,
1853 combined_bindings,
1854 &nested_effective,
1855 type_resolver,
1856 ) {
1857 self.errors.extend(errs);
1858 }
1859 }
1860 }
1861
1862 let expected_key_len = current_segments.len() + 1;
1865 for (binding_key, (_, binding_source)) in &data_bindings {
1866 if binding_key.len() == expected_key_len
1867 && binding_key[..current_segments.len()]
1868 .iter()
1869 .zip(current_segments.iter())
1870 .all(|(a, b)| a == &b.data)
1871 && !used_binding_keys.contains(binding_key)
1872 {
1873 self.errors.push(self.engine_error(
1874 format!(
1875 "Data binding targets a data that does not exist in the referenced spec: '{}'",
1876 binding_key.join(".")
1877 ),
1878 binding_source,
1879 ));
1880 }
1881 }
1882
1883 let rule_names: HashSet<&str> = spec.rules.iter().map(|r| r.name.as_str()).collect();
1884 for rule in &spec.rules {
1885 self.add_rule(
1886 rule,
1887 spec_arc,
1888 &data_map,
1889 ¤t_segments,
1890 &rule_names,
1891 effective,
1892 );
1893 }
1894
1895 Ok(())
1896 }
1897
1898 fn add_rule(
1899 &mut self,
1900 rule: &LemmaRule,
1901 current_spec_arc: &Arc<LemmaSpec>,
1902 data_map: &HashMap<String, &LemmaData>,
1903 current_segments: &[PathSegment],
1904 rule_names: &HashSet<&str>,
1905 effective: &EffectiveDate,
1906 ) {
1907 let rule_path = RulePath {
1908 segments: current_segments.to_vec(),
1909 rule: rule.name.clone(),
1910 };
1911
1912 if self.rules.contains_key(&rule_path) {
1913 let rule_source = &rule.source_location;
1914 self.errors.push(
1915 self.engine_error(format!("Duplicate rule '{}'", rule_path.rule), rule_source),
1916 );
1917 return;
1918 }
1919
1920 let mut branches = Vec::new();
1921 let mut depends_on_rules = BTreeSet::new();
1922
1923 let converted_expression = match self.convert_expression_and_extract_dependencies(
1924 &rule.expression,
1925 current_spec_arc,
1926 data_map,
1927 current_segments,
1928 &mut depends_on_rules,
1929 rule_names,
1930 effective,
1931 ) {
1932 Some(expr) => expr,
1933 None => return,
1934 };
1935 branches.push((None, converted_expression));
1936
1937 for unless_clause in &rule.unless_clauses {
1938 let converted_condition = match self.convert_expression_and_extract_dependencies(
1939 &unless_clause.condition,
1940 current_spec_arc,
1941 data_map,
1942 current_segments,
1943 &mut depends_on_rules,
1944 rule_names,
1945 effective,
1946 ) {
1947 Some(expr) => expr,
1948 None => return,
1949 };
1950 let converted_result = match self.convert_expression_and_extract_dependencies(
1951 &unless_clause.result,
1952 current_spec_arc,
1953 data_map,
1954 current_segments,
1955 &mut depends_on_rules,
1956 rule_names,
1957 effective,
1958 ) {
1959 Some(expr) => expr,
1960 None => return,
1961 };
1962 branches.push((Some(converted_condition), converted_result));
1963 }
1964
1965 let rule_node = RuleNode {
1966 branches,
1967 source: rule.source_location.clone(),
1968 depends_on_rules,
1969 rule_type: LemmaType::veto_type(),
1970 spec_name: current_spec_arc.name.clone(),
1971 };
1972
1973 self.rules.insert(rule_path, rule_node);
1974 }
1975
1976 #[allow(clippy::too_many_arguments)]
1978 fn convert_binary_operands(
1979 &mut self,
1980 left: &ast::Expression,
1981 right: &ast::Expression,
1982 current_spec_arc: &Arc<LemmaSpec>,
1983 data_map: &HashMap<String, &LemmaData>,
1984 current_segments: &[PathSegment],
1985 depends_on_rules: &mut BTreeSet<RulePath>,
1986 rule_names: &HashSet<&str>,
1987 effective: &EffectiveDate,
1988 ) -> Option<(Expression, Expression)> {
1989 let converted_left = self.convert_expression_and_extract_dependencies(
1990 left,
1991 current_spec_arc,
1992 data_map,
1993 current_segments,
1994 depends_on_rules,
1995 rule_names,
1996 effective,
1997 )?;
1998 let converted_right = self.convert_expression_and_extract_dependencies(
1999 right,
2000 current_spec_arc,
2001 data_map,
2002 current_segments,
2003 depends_on_rules,
2004 rule_names,
2005 effective,
2006 )?;
2007 Some((converted_left, converted_right))
2008 }
2009
2010 #[allow(clippy::too_many_arguments)]
2012 fn convert_expression_and_extract_dependencies(
2013 &mut self,
2014 expr: &ast::Expression,
2015 current_spec_arc: &Arc<LemmaSpec>,
2016 data_map: &HashMap<String, &LemmaData>,
2017 current_segments: &[PathSegment],
2018 depends_on_rules: &mut BTreeSet<RulePath>,
2019 rule_names: &HashSet<&str>,
2020 effective: &EffectiveDate,
2021 ) -> Option<Expression> {
2022 let expr_src = expr
2023 .source_location
2024 .as_ref()
2025 .expect("BUG: AST expression missing source location");
2026 match &expr.kind {
2027 ast::ExpressionKind::Reference(r) => {
2028 let expr_source = expr_src;
2029 let (segments, target_arc_opt) = if r.segments.is_empty() {
2030 (current_segments.to_vec(), None)
2031 } else {
2032 let data_map_owned: HashMap<String, LemmaData> = data_map
2033 .iter()
2034 .map(|(k, v)| (k.clone(), (*v).clone()))
2035 .collect();
2036 let (segs, arc) = self.resolve_path_segments(
2037 &r.segments,
2038 expr_source,
2039 data_map_owned,
2040 current_segments.to_vec(),
2041 effective,
2042 )?;
2043 (segs, Some(arc))
2044 };
2045
2046 let (is_data, is_rule, target_spec_name_opt) = match &target_arc_opt {
2047 None => {
2048 let is_data = data_map.contains_key(&r.name);
2049 let is_rule = rule_names.contains(r.name.as_str());
2050 (is_data, is_rule, None)
2051 }
2052 Some(target_arc) => {
2053 let target_spec = target_arc.as_ref();
2054 let target_data_names: HashSet<&str> = target_spec
2055 .data
2056 .iter()
2057 .filter(|f| f.reference.is_local())
2058 .map(|f| f.reference.name.as_str())
2059 .collect();
2060 let target_rule_names: HashSet<&str> =
2061 target_spec.rules.iter().map(|r| r.name.as_str()).collect();
2062 let is_data = target_data_names.contains(r.name.as_str());
2063 let is_rule = target_rule_names.contains(r.name.as_str());
2064 (is_data, is_rule, Some(target_spec.name.as_str()))
2065 }
2066 };
2067
2068 if is_data && is_rule {
2069 self.errors.push(self.engine_error(
2070 format!("'{}' is both a data and a rule", r.name),
2071 expr_source,
2072 ));
2073 return None;
2074 }
2075 if is_data {
2076 let data_path = DataPath {
2077 segments,
2078 data: r.name.clone(),
2079 };
2080 return Some(Expression {
2081 kind: ExpressionKind::DataPath(data_path),
2082 source_location: expr.source_location.clone(),
2083 });
2084 }
2085 if is_rule {
2086 let rule_path = RulePath {
2087 segments,
2088 rule: r.name.clone(),
2089 };
2090 depends_on_rules.insert(rule_path.clone());
2091 return Some(Expression {
2092 kind: ExpressionKind::RulePath(rule_path),
2093 source_location: expr.source_location.clone(),
2094 });
2095 }
2096 let msg = match target_spec_name_opt {
2097 Some(s) => format!("Reference '{}' not found in spec '{}'", r.name, s),
2098 None => format!("Reference '{}' not found", r.name),
2099 };
2100 self.errors.push(self.engine_error(msg, expr_source));
2101 None
2102 }
2103
2104 ast::ExpressionKind::LogicalAnd(left, right) => {
2105 let (l, r) = self.convert_binary_operands(
2106 left,
2107 right,
2108 current_spec_arc,
2109 data_map,
2110 current_segments,
2111 depends_on_rules,
2112 rule_names,
2113 effective,
2114 )?;
2115 Some(Expression {
2116 kind: ExpressionKind::LogicalAnd(Arc::new(l), Arc::new(r)),
2117 source_location: expr.source_location.clone(),
2118 })
2119 }
2120
2121 ast::ExpressionKind::Arithmetic(left, op, right) => {
2122 let (l, r) = self.convert_binary_operands(
2123 left,
2124 right,
2125 current_spec_arc,
2126 data_map,
2127 current_segments,
2128 depends_on_rules,
2129 rule_names,
2130 effective,
2131 )?;
2132 Some(Expression {
2133 kind: ExpressionKind::Arithmetic(Arc::new(l), op.clone(), Arc::new(r)),
2134 source_location: expr.source_location.clone(),
2135 })
2136 }
2137
2138 ast::ExpressionKind::Comparison(left, op, right) => {
2139 let (l, r) = self.convert_binary_operands(
2140 left,
2141 right,
2142 current_spec_arc,
2143 data_map,
2144 current_segments,
2145 depends_on_rules,
2146 rule_names,
2147 effective,
2148 )?;
2149 Some(Expression {
2150 kind: ExpressionKind::Comparison(Arc::new(l), op.clone(), Arc::new(r)),
2151 source_location: expr.source_location.clone(),
2152 })
2153 }
2154
2155 ast::ExpressionKind::UnitConversion(value, target) => {
2156 let converted_value = self.convert_expression_and_extract_dependencies(
2157 value,
2158 current_spec_arc,
2159 data_map,
2160 current_segments,
2161 depends_on_rules,
2162 rule_names,
2163 effective,
2164 )?;
2165
2166 let resolved_spec_types = self.local_types.get(current_spec_arc);
2167 let unit_index = resolved_spec_types.map(|dt| &dt.unit_index);
2168 let semantic_target = match conversion_target_to_semantic(target, unit_index) {
2169 Ok(t) => t,
2170 Err(msg) => {
2171 let full_msg = unit_index
2174 .map(|idx| {
2175 let valid: Vec<&str> = idx.keys().map(String::as_str).collect();
2176 format!("{} Valid units: {}", msg, valid.join(", "))
2177 })
2178 .unwrap_or(msg);
2179 self.errors.push(Error::validation_with_context(
2180 full_msg,
2181 expr.source_location.clone(),
2182 None::<String>,
2183 Some(Arc::clone(&self.main_spec)),
2184 None,
2185 ));
2186 return None;
2187 }
2188 };
2189
2190 Some(Expression {
2191 kind: ExpressionKind::UnitConversion(
2192 Arc::new(converted_value),
2193 semantic_target,
2194 ),
2195 source_location: expr.source_location.clone(),
2196 })
2197 }
2198
2199 ast::ExpressionKind::LogicalNegation(operand, neg_type) => {
2200 let converted_operand = self.convert_expression_and_extract_dependencies(
2201 operand,
2202 current_spec_arc,
2203 data_map,
2204 current_segments,
2205 depends_on_rules,
2206 rule_names,
2207 effective,
2208 )?;
2209 Some(Expression {
2210 kind: ExpressionKind::LogicalNegation(
2211 Arc::new(converted_operand),
2212 neg_type.clone(),
2213 ),
2214 source_location: expr.source_location.clone(),
2215 })
2216 }
2217
2218 ast::ExpressionKind::MathematicalComputation(op, operand) => {
2219 let converted_operand = self.convert_expression_and_extract_dependencies(
2220 operand,
2221 current_spec_arc,
2222 data_map,
2223 current_segments,
2224 depends_on_rules,
2225 rule_names,
2226 effective,
2227 )?;
2228 Some(Expression {
2229 kind: ExpressionKind::MathematicalComputation(
2230 op.clone(),
2231 Arc::new(converted_operand),
2232 ),
2233 source_location: expr.source_location.clone(),
2234 })
2235 }
2236
2237 ast::ExpressionKind::Literal(value) => {
2238 let semantic_value = match value_to_semantic(value) {
2240 Ok(v) => v,
2241 Err(e) => {
2242 self.errors.push(self.engine_error(e, expr_src));
2243 return None;
2244 }
2245 };
2246 let lemma_type = match value {
2248 Value::Text(_) => primitive_text().clone(),
2249 Value::Number(_) => primitive_number().clone(),
2250 Value::Scale(_, unit) => {
2251 match self
2252 .local_types
2253 .get(current_spec_arc)
2254 .and_then(|dt| dt.unit_index.get(unit))
2255 {
2256 Some(lt) => lt.clone(),
2257 None => {
2258 self.errors.push(self.engine_error(
2259 format!(
2260 "Scale literal uses unknown unit '{}' for this spec",
2261 unit
2262 ),
2263 expr_src,
2264 ));
2265 return None;
2266 }
2267 }
2268 }
2269 Value::Boolean(_) => primitive_boolean().clone(),
2270 Value::Date(_) => primitive_date().clone(),
2271 Value::Time(_) => primitive_time().clone(),
2272 Value::Duration(_, _) => primitive_duration().clone(),
2273 Value::Ratio(_, _) => primitive_ratio().clone(),
2274 };
2275 let literal_value = LiteralValue {
2276 value: semantic_value,
2277 lemma_type,
2278 };
2279 Some(Expression {
2280 kind: ExpressionKind::Literal(Box::new(literal_value)),
2281 source_location: expr.source_location.clone(),
2282 })
2283 }
2284
2285 ast::ExpressionKind::Veto(veto_expression) => Some(Expression {
2286 kind: ExpressionKind::Veto(veto_expression.clone()),
2287 source_location: expr.source_location.clone(),
2288 }),
2289
2290 ast::ExpressionKind::UnresolvedUnitLiteral(value, unit) => {
2291 if let Some(lt) = self
2292 .local_types
2293 .get(current_spec_arc)
2294 .and_then(|dt| dt.unit_index.get(unit))
2295 {
2296 let semantic_value = ValueKind::Scale(*value, unit.clone());
2297 let literal_value = LiteralValue {
2298 value: semantic_value,
2299 lemma_type: lt.clone(),
2300 };
2301 Some(Expression {
2302 kind: ExpressionKind::Literal(Box::new(literal_value)),
2303 source_location: expr.source_location.clone(),
2304 })
2305 } else {
2306 self.errors
2307 .push(self.engine_error(format!("Unknown unit '{}'", unit), expr_src));
2308 None
2309 }
2310 }
2311
2312 ast::ExpressionKind::Now => Some(Expression {
2313 kind: ExpressionKind::Now,
2314 source_location: expr.source_location.clone(),
2315 }),
2316
2317 ast::ExpressionKind::DateRelative(kind, date_expr, tolerance) => {
2318 let converted_date = self.convert_expression_and_extract_dependencies(
2319 date_expr,
2320 current_spec_arc,
2321 data_map,
2322 current_segments,
2323 depends_on_rules,
2324 rule_names,
2325 effective,
2326 )?;
2327 let converted_tolerance = match tolerance {
2328 Some(tol) => Some(Arc::new(self.convert_expression_and_extract_dependencies(
2329 tol,
2330 current_spec_arc,
2331 data_map,
2332 current_segments,
2333 depends_on_rules,
2334 rule_names,
2335 effective,
2336 )?)),
2337 None => None,
2338 };
2339 Some(Expression {
2340 kind: ExpressionKind::DateRelative(
2341 *kind,
2342 Arc::new(converted_date),
2343 converted_tolerance,
2344 ),
2345 source_location: expr.source_location.clone(),
2346 })
2347 }
2348
2349 ast::ExpressionKind::DateCalendar(kind, unit, date_expr) => {
2350 let converted_date = self.convert_expression_and_extract_dependencies(
2351 date_expr,
2352 current_spec_arc,
2353 data_map,
2354 current_segments,
2355 depends_on_rules,
2356 rule_names,
2357 effective,
2358 )?;
2359 Some(Expression {
2360 kind: ExpressionKind::DateCalendar(*kind, *unit, Arc::new(converted_date)),
2361 source_location: expr.source_location.clone(),
2362 })
2363 }
2364 }
2365 }
2366}
2367
2368fn find_types_by_name<'b>(
2371 types: &'b ResolvedTypesMap,
2372 name: &str,
2373) -> Option<&'b ResolvedSpecTypes> {
2374 types
2375 .iter()
2376 .find(|(spec, _)| spec.name == name)
2377 .map(|(_, t)| t)
2378}
2379
2380fn compute_arithmetic_result_type(left_type: LemmaType, right_type: LemmaType) -> LemmaType {
2381 compute_arithmetic_result_type_recursive(left_type, right_type, false)
2382}
2383
2384fn compute_arithmetic_result_type_recursive(
2385 left_type: LemmaType,
2386 right_type: LemmaType,
2387 swapped: bool,
2388) -> LemmaType {
2389 match (&left_type.specifications, &right_type.specifications) {
2390 (TypeSpecification::Veto { .. }, _) | (_, TypeSpecification::Veto { .. }) => {
2391 LemmaType::veto_type()
2392 }
2393 (TypeSpecification::Undetermined, _) => LemmaType::undetermined_type(),
2394
2395 (TypeSpecification::Date { .. }, TypeSpecification::Date { .. }) => {
2396 primitive_duration().clone()
2397 }
2398 (TypeSpecification::Date { .. }, TypeSpecification::Time { .. }) => {
2399 primitive_duration().clone()
2400 }
2401 (TypeSpecification::Time { .. }, TypeSpecification::Time { .. }) => {
2402 primitive_duration().clone()
2403 }
2404
2405 _ if left_type == right_type => left_type,
2406
2407 (TypeSpecification::Date { .. }, TypeSpecification::Duration { .. }) => left_type,
2408 (TypeSpecification::Time { .. }, TypeSpecification::Duration { .. }) => left_type,
2409
2410 (TypeSpecification::Scale { .. }, TypeSpecification::Ratio { .. }) => left_type,
2411 (TypeSpecification::Scale { .. }, TypeSpecification::Number { .. }) => left_type,
2412 (TypeSpecification::Scale { .. }, TypeSpecification::Duration { .. }) => {
2413 primitive_number().clone()
2414 }
2415 (TypeSpecification::Scale { .. }, TypeSpecification::Scale { .. }) => left_type,
2416
2417 (TypeSpecification::Duration { .. }, TypeSpecification::Number { .. }) => left_type,
2418 (TypeSpecification::Duration { .. }, TypeSpecification::Ratio { .. }) => left_type,
2419 (TypeSpecification::Duration { .. }, TypeSpecification::Duration { .. }) => {
2420 primitive_duration().clone()
2421 }
2422
2423 (TypeSpecification::Number { .. }, TypeSpecification::Ratio { .. }) => {
2424 primitive_number().clone()
2425 }
2426 (TypeSpecification::Number { .. }, TypeSpecification::Number { .. }) => {
2427 primitive_number().clone()
2428 }
2429
2430 (TypeSpecification::Ratio { .. }, TypeSpecification::Ratio { .. }) => left_type,
2431
2432 _ => {
2433 if swapped {
2434 LemmaType::undetermined_type()
2435 } else {
2436 compute_arithmetic_result_type_recursive(right_type, left_type, true)
2437 }
2438 }
2439 }
2440}
2441
2442fn infer_expression_type(
2449 expression: &Expression,
2450 graph: &Graph,
2451 computed_rule_types: &HashMap<RulePath, LemmaType>,
2452 resolved_types: &ResolvedTypesMap,
2453 spec_name: &str,
2454) -> LemmaType {
2455 match &expression.kind {
2456 ExpressionKind::Literal(literal_value) => literal_value.as_ref().get_type().clone(),
2457
2458 ExpressionKind::DataPath(data_path) => {
2459 infer_data_type(data_path, graph, computed_rule_types)
2460 }
2461
2462 ExpressionKind::RulePath(rule_path) => computed_rule_types
2463 .get(rule_path)
2464 .cloned()
2465 .unwrap_or_else(LemmaType::undetermined_type),
2466
2467 ExpressionKind::LogicalAnd(left, right) => {
2468 let left_type =
2469 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_name);
2470 let right_type =
2471 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_name);
2472 if left_type.vetoed() || right_type.vetoed() {
2473 return LemmaType::veto_type();
2474 }
2475 if left_type.is_undetermined() || right_type.is_undetermined() {
2476 return LemmaType::undetermined_type();
2477 }
2478 primitive_boolean().clone()
2479 }
2480
2481 ExpressionKind::LogicalNegation(operand, _) => {
2482 let operand_type = infer_expression_type(
2483 operand,
2484 graph,
2485 computed_rule_types,
2486 resolved_types,
2487 spec_name,
2488 );
2489 if operand_type.vetoed() {
2490 return LemmaType::veto_type();
2491 }
2492 if operand_type.is_undetermined() {
2493 return LemmaType::undetermined_type();
2494 }
2495 primitive_boolean().clone()
2496 }
2497
2498 ExpressionKind::Comparison(left, _op, right) => {
2499 let left_type =
2500 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_name);
2501 let right_type =
2502 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_name);
2503 if left_type.vetoed() || right_type.vetoed() {
2504 return LemmaType::veto_type();
2505 }
2506 if left_type.is_undetermined() || right_type.is_undetermined() {
2507 return LemmaType::undetermined_type();
2508 }
2509 primitive_boolean().clone()
2510 }
2511
2512 ExpressionKind::Arithmetic(left, _operator, right) => {
2513 let left_type =
2514 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_name);
2515 let right_type =
2516 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_name);
2517 compute_arithmetic_result_type(left_type, right_type)
2518 }
2519
2520 ExpressionKind::UnitConversion(source_expression, target) => {
2521 let source_type = infer_expression_type(
2522 source_expression,
2523 graph,
2524 computed_rule_types,
2525 resolved_types,
2526 spec_name,
2527 );
2528 if source_type.vetoed() {
2529 return LemmaType::veto_type();
2530 }
2531 if source_type.is_undetermined() {
2532 return LemmaType::undetermined_type();
2533 }
2534 match target {
2535 SemanticConversionTarget::Duration(_) => primitive_duration().clone(),
2536 SemanticConversionTarget::ScaleUnit(unit_name) => {
2537 if source_type.is_number() {
2538 find_types_by_name(resolved_types, spec_name)
2539 .and_then(|dt| dt.unit_index.get(unit_name))
2540 .cloned()
2541 .unwrap_or_else(LemmaType::undetermined_type)
2542 } else {
2543 source_type
2544 }
2545 }
2546 SemanticConversionTarget::RatioUnit(unit_name) => {
2547 if source_type.is_number() {
2548 find_types_by_name(resolved_types, spec_name)
2549 .and_then(|dt| dt.unit_index.get(unit_name))
2550 .cloned()
2551 .unwrap_or_else(LemmaType::undetermined_type)
2552 } else {
2553 source_type
2554 }
2555 }
2556 }
2557 }
2558
2559 ExpressionKind::MathematicalComputation(_, operand) => {
2560 let operand_type = infer_expression_type(
2561 operand,
2562 graph,
2563 computed_rule_types,
2564 resolved_types,
2565 spec_name,
2566 );
2567 if operand_type.vetoed() {
2568 return LemmaType::veto_type();
2569 }
2570 if operand_type.is_undetermined() {
2571 return LemmaType::undetermined_type();
2572 }
2573 primitive_number().clone()
2574 }
2575
2576 ExpressionKind::Veto(_) => LemmaType::veto_type(),
2577
2578 ExpressionKind::Now => primitive_date().clone(),
2579
2580 ExpressionKind::DateRelative(..) | ExpressionKind::DateCalendar(..) => {
2581 primitive_boolean().clone()
2582 }
2583 }
2584}
2585
2586fn infer_data_type(
2595 data_path: &DataPath,
2596 graph: &Graph,
2597 computed_rule_types: &HashMap<RulePath, LemmaType>,
2598) -> LemmaType {
2599 let entry = match graph.data().get(data_path) {
2600 Some(e) => e,
2601 None => return LemmaType::undetermined_type(),
2602 };
2603 match entry {
2604 DataDefinition::Value { value, .. } => value.lemma_type.clone(),
2605 DataDefinition::TypeDeclaration { resolved_type, .. } => resolved_type.clone(),
2606 DataDefinition::Reference {
2607 target: ReferenceTarget::Rule(target_rule),
2608 resolved_type,
2609 ..
2610 } => {
2611 if !resolved_type.is_undetermined() {
2612 resolved_type.clone()
2613 } else {
2614 computed_rule_types
2615 .get(target_rule)
2616 .cloned()
2617 .unwrap_or_else(LemmaType::undetermined_type)
2618 }
2619 }
2620 DataDefinition::Reference { resolved_type, .. } => resolved_type.clone(),
2621 DataDefinition::SpecRef { .. } => LemmaType::undetermined_type(),
2622 }
2623}
2624
2625fn collect_rule_reference_dependencies(
2632 expression: &Expression,
2633 reference_to_rule: &HashMap<DataPath, RulePath>,
2634 out: &mut BTreeSet<RulePath>,
2635) {
2636 let mut paths: HashSet<DataPath> = HashSet::new();
2637 expression.kind.collect_data_paths(&mut paths);
2638 for path in paths {
2639 if let Some(target_rule) = reference_to_rule.get(&path) {
2640 out.insert(target_rule.clone());
2641 }
2642 }
2643}
2644
2645fn engine_error_at_graph(graph: &Graph, source: &Source, message: impl Into<String>) -> Error {
2650 Error::validation_with_context(
2651 message.into(),
2652 Some(source.clone()),
2653 None::<String>,
2654 Some(Arc::clone(&graph.main_spec)),
2655 None,
2656 )
2657}
2658
2659fn check_logical_operands(
2660 graph: &Graph,
2661 left_type: &LemmaType,
2662 right_type: &LemmaType,
2663 source: &Source,
2664) -> Result<(), Vec<Error>> {
2665 if left_type.vetoed() || right_type.vetoed() {
2666 return Ok(());
2667 }
2668 let mut errors = Vec::new();
2669 if !left_type.is_boolean() {
2670 errors.push(engine_error_at_graph(
2671 graph,
2672 source,
2673 format!(
2674 "Logical operation requires boolean operands, got {:?} for left operand",
2675 left_type
2676 ),
2677 ));
2678 }
2679 if !right_type.is_boolean() {
2680 errors.push(engine_error_at_graph(
2681 graph,
2682 source,
2683 format!(
2684 "Logical operation requires boolean operands, got {:?} for right operand",
2685 right_type
2686 ),
2687 ));
2688 }
2689 if errors.is_empty() {
2690 Ok(())
2691 } else {
2692 Err(errors)
2693 }
2694}
2695
2696fn check_logical_operand(
2697 graph: &Graph,
2698 operand_type: &LemmaType,
2699 source: &Source,
2700) -> Result<(), Vec<Error>> {
2701 if operand_type.vetoed() {
2702 return Ok(());
2703 }
2704 if !operand_type.is_boolean() {
2705 Err(vec![engine_error_at_graph(
2706 graph,
2707 source,
2708 format!(
2709 "Logical negation requires boolean operand, got {:?}",
2710 operand_type
2711 ),
2712 )])
2713 } else {
2714 Ok(())
2715 }
2716}
2717
2718fn check_comparison_types(
2719 graph: &Graph,
2720 left_type: &LemmaType,
2721 op: &ComparisonComputation,
2722 right_type: &LemmaType,
2723 source: &Source,
2724) -> Result<(), Vec<Error>> {
2725 if left_type.vetoed() || right_type.vetoed() {
2726 return Ok(());
2727 }
2728 let is_equality_only = matches!(op, ComparisonComputation::Is | ComparisonComputation::IsNot);
2729
2730 if left_type.is_boolean() && right_type.is_boolean() {
2731 if !is_equality_only {
2732 return Err(vec![engine_error_at_graph(
2733 graph,
2734 source,
2735 format!("Can only use 'is' and 'is not' with booleans (got {})", op),
2736 )]);
2737 }
2738 return Ok(());
2739 }
2740
2741 if left_type.is_text() && right_type.is_text() {
2742 if !is_equality_only {
2743 return Err(vec![engine_error_at_graph(
2744 graph,
2745 source,
2746 format!("Can only use 'is' and 'is not' with text (got {})", op),
2747 )]);
2748 }
2749 return Ok(());
2750 }
2751
2752 if left_type.is_number() && right_type.is_number() {
2753 return Ok(());
2754 }
2755
2756 if left_type.is_ratio() && right_type.is_ratio() {
2757 return Ok(());
2758 }
2759
2760 if left_type.is_date() && right_type.is_date() {
2761 return Ok(());
2762 }
2763
2764 if left_type.is_time() && right_type.is_time() {
2765 return Ok(());
2766 }
2767
2768 if left_type.is_scale() && right_type.is_scale() {
2769 if !left_type.same_scale_family(right_type) {
2770 return Err(vec![engine_error_at_graph(
2771 graph,
2772 source,
2773 format!(
2774 "Cannot compare different scale types: {} and {}",
2775 left_type.name(),
2776 right_type.name()
2777 ),
2778 )]);
2779 }
2780 return Ok(());
2781 }
2782
2783 if left_type.is_duration() && right_type.is_duration() {
2784 return Ok(());
2785 }
2786 if left_type.is_duration() && right_type.is_number() {
2787 return Ok(());
2788 }
2789 if left_type.is_number() && right_type.is_duration() {
2790 return Ok(());
2791 }
2792
2793 Err(vec![engine_error_at_graph(
2794 graph,
2795 source,
2796 format!("Cannot compare {:?} with {:?}", left_type, right_type),
2797 )])
2798}
2799
2800fn check_arithmetic_types(
2801 graph: &Graph,
2802 left_type: &LemmaType,
2803 right_type: &LemmaType,
2804 operator: &ArithmeticComputation,
2805 source: &Source,
2806) -> Result<(), Vec<Error>> {
2807 if left_type.vetoed() || right_type.vetoed() {
2808 return Ok(());
2809 }
2810 if left_type.is_date() || left_type.is_time() || right_type.is_date() || right_type.is_time() {
2812 let both_temporal = (left_type.is_date() || left_type.is_time())
2813 && (right_type.is_date() || right_type.is_time());
2814 let one_is_duration = left_type.is_duration() || right_type.is_duration();
2815 let valid = matches!(
2816 operator,
2817 ArithmeticComputation::Add | ArithmeticComputation::Subtract
2818 ) && (both_temporal || one_is_duration);
2819 if !valid {
2820 return Err(vec![engine_error_at_graph(
2821 graph,
2822 source,
2823 format!(
2824 "Cannot apply '{}' to {} and {}.",
2825 operator,
2826 left_type.name(),
2827 right_type.name()
2828 ),
2829 )]);
2830 }
2831 return Ok(());
2832 }
2833
2834 if left_type.is_scale() && right_type.is_scale() && !left_type.same_scale_family(right_type) {
2836 return Err(vec![engine_error_at_graph(
2837 graph,
2838 source,
2839 format!(
2840 "Cannot {} different scale types: {} and {}. Operations between different scale types produce ambiguous result units.",
2841 match operator {
2842 ArithmeticComputation::Add => "add",
2843 ArithmeticComputation::Subtract => "subtract",
2844 ArithmeticComputation::Multiply => "multiply",
2845 ArithmeticComputation::Divide => "divide",
2846 ArithmeticComputation::Modulo => "modulo",
2847 ArithmeticComputation::Power => "power",
2848 },
2849 left_type.name(),
2850 right_type.name()
2851 ),
2852 )]);
2853 }
2854
2855 let left_valid = left_type.is_scale()
2857 || left_type.is_number()
2858 || left_type.is_duration()
2859 || left_type.is_ratio();
2860 let right_valid = right_type.is_scale()
2861 || right_type.is_number()
2862 || right_type.is_duration()
2863 || right_type.is_ratio();
2864
2865 if !left_valid || !right_valid {
2866 return Err(vec![engine_error_at_graph(
2867 graph,
2868 source,
2869 format!(
2870 "Cannot apply '{}' to {} and {}.",
2871 operator,
2872 left_type.name(),
2873 right_type.name()
2874 ),
2875 )]);
2876 }
2877
2878 if left_type.has_same_base_type(right_type) {
2880 return Ok(());
2881 }
2882
2883 let pair = |a: fn(&LemmaType) -> bool, b: fn(&LemmaType) -> bool| {
2884 (a(left_type) && b(right_type)) || (b(left_type) && a(right_type))
2885 };
2886
2887 let allowed = match operator {
2888 ArithmeticComputation::Multiply => {
2889 pair(LemmaType::is_scale, LemmaType::is_number)
2890 || pair(LemmaType::is_scale, LemmaType::is_ratio)
2891 || pair(LemmaType::is_scale, LemmaType::is_duration)
2892 || pair(LemmaType::is_duration, LemmaType::is_number)
2893 || pair(LemmaType::is_duration, LemmaType::is_ratio)
2894 || pair(LemmaType::is_number, LemmaType::is_ratio)
2895 }
2896 ArithmeticComputation::Divide => {
2897 pair(LemmaType::is_scale, LemmaType::is_number)
2898 || pair(LemmaType::is_scale, LemmaType::is_ratio)
2899 || pair(LemmaType::is_scale, LemmaType::is_duration)
2900 || (left_type.is_duration() && right_type.is_number())
2901 || (left_type.is_duration() && right_type.is_ratio())
2902 || pair(LemmaType::is_number, LemmaType::is_ratio)
2903 }
2904 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
2905 pair(LemmaType::is_scale, LemmaType::is_number)
2906 || pair(LemmaType::is_scale, LemmaType::is_ratio)
2907 || pair(LemmaType::is_duration, LemmaType::is_number)
2908 || pair(LemmaType::is_duration, LemmaType::is_ratio)
2909 || pair(LemmaType::is_number, LemmaType::is_ratio)
2910 }
2911 ArithmeticComputation::Power => {
2912 (left_type.is_number()
2913 || left_type.is_scale()
2914 || left_type.is_ratio()
2915 || left_type.is_duration())
2916 && (right_type.is_number() || right_type.is_ratio())
2917 }
2918 ArithmeticComputation::Modulo => right_type.is_number() || right_type.is_ratio(),
2919 };
2920
2921 if !allowed {
2922 return Err(vec![engine_error_at_graph(
2923 graph,
2924 source,
2925 format!(
2926 "Cannot apply '{}' to {} and {}.",
2927 operator,
2928 left_type.name(),
2929 right_type.name(),
2930 ),
2931 )]);
2932 }
2933
2934 Ok(())
2935}
2936
2937fn check_unit_conversion_types(
2938 graph: &Graph,
2939 source_type: &LemmaType,
2940 target: &SemanticConversionTarget,
2941 resolved_types: &ResolvedTypesMap,
2942 source: &Source,
2943 spec_name: &str,
2944) -> Result<(), Vec<Error>> {
2945 if source_type.vetoed() {
2946 return Ok(());
2947 }
2948 match target {
2949 SemanticConversionTarget::ScaleUnit(unit_name)
2950 | SemanticConversionTarget::RatioUnit(unit_name) => {
2951 let unit_check: Option<(bool, Vec<&str>)> = match (&source_type.specifications, target)
2952 {
2953 (
2954 TypeSpecification::Scale { units, .. },
2955 SemanticConversionTarget::ScaleUnit(_),
2956 ) => {
2957 let valid: Vec<&str> = units.iter().map(|u| u.name.as_str()).collect();
2958 let found = units.iter().any(|u| u.name.eq_ignore_ascii_case(unit_name));
2959 Some((found, valid))
2960 }
2961 (
2962 TypeSpecification::Ratio { units, .. },
2963 SemanticConversionTarget::RatioUnit(_),
2964 ) => {
2965 let valid: Vec<&str> = units.iter().map(|u| u.name.as_str()).collect();
2966 let found = units.iter().any(|u| u.name.eq_ignore_ascii_case(unit_name));
2967 Some((found, valid))
2968 }
2969 _ => None,
2970 };
2971
2972 match unit_check {
2973 Some((true, _)) => Ok(()),
2974 Some((false, valid)) => Err(vec![engine_error_at_graph(
2975 graph,
2976 source,
2977 format!(
2978 "Unknown unit '{}' for type {}. Valid units: {}",
2979 unit_name,
2980 source_type.name(),
2981 valid.join(", ")
2982 ),
2983 )]),
2984 None if source_type.is_number() => {
2985 if find_types_by_name(resolved_types, spec_name)
2986 .and_then(|dt| dt.unit_index.get(unit_name))
2987 .is_none()
2988 {
2989 Err(vec![engine_error_at_graph(
2990 graph,
2991 source,
2992 format!("Unknown unit '{}' in spec '{}'.", unit_name, spec_name),
2993 )])
2994 } else {
2995 Ok(())
2996 }
2997 }
2998 None => Err(vec![engine_error_at_graph(
2999 graph,
3000 source,
3001 format!(
3002 "Cannot convert {} to unit '{}'.",
3003 source_type.name(),
3004 unit_name
3005 ),
3006 )]),
3007 }
3008 }
3009 SemanticConversionTarget::Duration(_) => {
3010 if !source_type.is_duration() && !source_type.is_numeric() {
3011 Err(vec![engine_error_at_graph(
3012 graph,
3013 source,
3014 format!("Cannot convert {} to duration.", source_type.name()),
3015 )])
3016 } else {
3017 Ok(())
3018 }
3019 }
3020 }
3021}
3022
3023fn check_mathematical_operand(
3024 graph: &Graph,
3025 operand_type: &LemmaType,
3026 source: &Source,
3027) -> Result<(), Vec<Error>> {
3028 if operand_type.vetoed() {
3029 return Ok(());
3030 }
3031 if !operand_type.is_number() {
3032 Err(vec![engine_error_at_graph(
3033 graph,
3034 source,
3035 format!(
3036 "Mathematical function requires number operand, got {:?}",
3037 operand_type
3038 ),
3039 )])
3040 } else {
3041 Ok(())
3042 }
3043}
3044
3045fn check_all_rule_references_exist(graph: &Graph) -> Result<(), Vec<Error>> {
3047 let mut errors = Vec::new();
3048 let existing_rules: HashSet<&RulePath> = graph.rules().keys().collect();
3049 for (rule_path, rule_node) in graph.rules() {
3050 for dependency in &rule_node.depends_on_rules {
3051 if !existing_rules.contains(dependency) {
3052 errors.push(engine_error_at_graph(
3053 graph,
3054 &rule_node.source,
3055 format!(
3056 "Rule '{}' references non-existent rule '{}'",
3057 rule_path.rule, dependency.rule
3058 ),
3059 ));
3060 }
3061 }
3062 }
3063 if errors.is_empty() {
3064 Ok(())
3065 } else {
3066 Err(errors)
3067 }
3068}
3069
3070fn check_data_and_rule_name_collisions(graph: &Graph) -> Result<(), Vec<Error>> {
3072 let mut errors = Vec::new();
3073 for rule_path in graph.rules().keys() {
3074 let data_path = DataPath::new(rule_path.segments.clone(), rule_path.rule.clone());
3075 if graph.data().contains_key(&data_path) {
3076 let rule_node = graph.rules().get(rule_path).unwrap_or_else(|| {
3077 unreachable!(
3078 "BUG: rule '{}' missing from graph while validating name collisions",
3079 rule_path.rule
3080 )
3081 });
3082 errors.push(engine_error_at_graph(
3083 graph,
3084 &rule_node.source,
3085 format!(
3086 "Name collision: '{}' is defined as both a data and a rule",
3087 data_path
3088 ),
3089 ));
3090 }
3091 }
3092 if errors.is_empty() {
3093 Ok(())
3094 } else {
3095 Err(errors)
3096 }
3097}
3098
3099fn check_data_reference(
3101 data_path: &DataPath,
3102 graph: &Graph,
3103 data_source: &Source,
3104) -> Result<(), Vec<Error>> {
3105 let entry = match graph.data().get(data_path) {
3106 Some(e) => e,
3107 None => {
3108 return Err(vec![engine_error_at_graph(
3109 graph,
3110 data_source,
3111 format!("Unknown data reference '{}'", data_path),
3112 )]);
3113 }
3114 };
3115 match entry {
3116 DataDefinition::Value { .. }
3117 | DataDefinition::TypeDeclaration { .. }
3118 | DataDefinition::Reference { .. } => Ok(()),
3119 DataDefinition::SpecRef { .. } => Err(vec![engine_error_at_graph(
3120 graph,
3121 entry.source(),
3122 format!(
3123 "Cannot compute type for spec reference data '{}'",
3124 data_path
3125 ),
3126 )]),
3127 }
3128}
3129
3130fn check_expression(
3134 expression: &Expression,
3135 graph: &Graph,
3136 inferred_types: &HashMap<RulePath, LemmaType>,
3137 resolved_types: &ResolvedTypesMap,
3138 spec_name: &str,
3139) -> Result<(), Vec<Error>> {
3140 let mut errors = Vec::new();
3141
3142 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
3143 if let Err(errs) = result {
3144 errors.extend(errs);
3145 }
3146 };
3147
3148 match &expression.kind {
3149 ExpressionKind::Literal(_) => {}
3150
3151 ExpressionKind::DataPath(data_path) => {
3152 let data_source = expression
3153 .source_location
3154 .as_ref()
3155 .expect("BUG: expression missing source in check_expression");
3156 collect(
3157 check_data_reference(data_path, graph, data_source),
3158 &mut errors,
3159 );
3160 }
3161
3162 ExpressionKind::RulePath(_) => {}
3163
3164 ExpressionKind::LogicalAnd(left, right) => {
3165 collect(
3166 check_expression(left, graph, inferred_types, resolved_types, spec_name),
3167 &mut errors,
3168 );
3169 collect(
3170 check_expression(right, graph, inferred_types, resolved_types, spec_name),
3171 &mut errors,
3172 );
3173
3174 let left_type =
3175 infer_expression_type(left, graph, inferred_types, resolved_types, spec_name);
3176 let right_type =
3177 infer_expression_type(right, graph, inferred_types, resolved_types, spec_name);
3178 let expr_source = expression
3179 .source_location
3180 .as_ref()
3181 .expect("BUG: expression missing source in check_expression");
3182 collect(
3183 check_logical_operands(graph, &left_type, &right_type, expr_source),
3184 &mut errors,
3185 );
3186 }
3187
3188 ExpressionKind::LogicalNegation(operand, _) => {
3189 collect(
3190 check_expression(operand, graph, inferred_types, resolved_types, spec_name),
3191 &mut errors,
3192 );
3193
3194 let operand_type =
3195 infer_expression_type(operand, graph, inferred_types, resolved_types, spec_name);
3196 let expr_source = expression
3197 .source_location
3198 .as_ref()
3199 .expect("BUG: expression missing source in check_expression");
3200 collect(
3201 check_logical_operand(graph, &operand_type, expr_source),
3202 &mut errors,
3203 );
3204 }
3205
3206 ExpressionKind::Comparison(left, op, right) => {
3207 collect(
3208 check_expression(left, graph, inferred_types, resolved_types, spec_name),
3209 &mut errors,
3210 );
3211 collect(
3212 check_expression(right, graph, inferred_types, resolved_types, spec_name),
3213 &mut errors,
3214 );
3215
3216 let left_type =
3217 infer_expression_type(left, graph, inferred_types, resolved_types, spec_name);
3218 let right_type =
3219 infer_expression_type(right, graph, inferred_types, resolved_types, spec_name);
3220 let expr_source = expression
3221 .source_location
3222 .as_ref()
3223 .expect("BUG: expression missing source in check_expression");
3224 collect(
3225 check_comparison_types(graph, &left_type, op, &right_type, expr_source),
3226 &mut errors,
3227 );
3228 }
3229
3230 ExpressionKind::Arithmetic(left, operator, right) => {
3231 collect(
3232 check_expression(left, graph, inferred_types, resolved_types, spec_name),
3233 &mut errors,
3234 );
3235 collect(
3236 check_expression(right, graph, inferred_types, resolved_types, spec_name),
3237 &mut errors,
3238 );
3239
3240 let left_type =
3241 infer_expression_type(left, graph, inferred_types, resolved_types, spec_name);
3242 let right_type =
3243 infer_expression_type(right, graph, inferred_types, resolved_types, spec_name);
3244 let expr_source = expression
3245 .source_location
3246 .as_ref()
3247 .expect("BUG: expression missing source in check_expression");
3248 collect(
3249 check_arithmetic_types(graph, &left_type, &right_type, operator, expr_source),
3250 &mut errors,
3251 );
3252 }
3253
3254 ExpressionKind::UnitConversion(source_expression, target) => {
3255 collect(
3256 check_expression(
3257 source_expression,
3258 graph,
3259 inferred_types,
3260 resolved_types,
3261 spec_name,
3262 ),
3263 &mut errors,
3264 );
3265
3266 let source_type = infer_expression_type(
3267 source_expression,
3268 graph,
3269 inferred_types,
3270 resolved_types,
3271 spec_name,
3272 );
3273 let expr_source = expression
3274 .source_location
3275 .as_ref()
3276 .expect("BUG: expression missing source in check_expression");
3277 collect(
3278 check_unit_conversion_types(
3279 graph,
3280 &source_type,
3281 target,
3282 resolved_types,
3283 expr_source,
3284 spec_name,
3285 ),
3286 &mut errors,
3287 );
3288
3289 if source_type.is_number() {
3290 match target {
3291 SemanticConversionTarget::ScaleUnit(unit_name)
3292 | SemanticConversionTarget::RatioUnit(unit_name) => {
3293 if find_types_by_name(resolved_types, spec_name)
3294 .and_then(|dt| dt.unit_index.get(unit_name))
3295 .is_none()
3296 {
3297 errors.push(engine_error_at_graph(
3298 graph,
3299 expr_source,
3300 format!(
3301 "Cannot resolve unit '{}' for spec '{}' (types may not have been resolved)",
3302 unit_name,
3303 spec_name
3304 ),
3305 ));
3306 }
3307 }
3308 SemanticConversionTarget::Duration(_) => {}
3309 }
3310 }
3311 }
3312
3313 ExpressionKind::MathematicalComputation(_, operand) => {
3314 collect(
3315 check_expression(operand, graph, inferred_types, resolved_types, spec_name),
3316 &mut errors,
3317 );
3318
3319 let operand_type =
3320 infer_expression_type(operand, graph, inferred_types, resolved_types, spec_name);
3321 let expr_source = expression
3322 .source_location
3323 .as_ref()
3324 .expect("BUG: expression missing source in check_expression");
3325 collect(
3326 check_mathematical_operand(graph, &operand_type, expr_source),
3327 &mut errors,
3328 );
3329 }
3330
3331 ExpressionKind::Veto(_) => {}
3332
3333 ExpressionKind::Now => {}
3334
3335 ExpressionKind::DateRelative(_, date_expr, tolerance) => {
3336 collect(
3337 check_expression(date_expr, graph, inferred_types, resolved_types, spec_name),
3338 &mut errors,
3339 );
3340
3341 let date_type =
3342 infer_expression_type(date_expr, graph, inferred_types, resolved_types, spec_name);
3343 if !date_type.is_date() {
3344 let expr_source = expression
3345 .source_location
3346 .as_ref()
3347 .expect("BUG: expression missing source in check_expression");
3348 errors.push(engine_error_at_graph(
3349 graph,
3350 expr_source,
3351 format!(
3352 "Date sugar 'in past/future' requires a date expression, got type '{}'",
3353 date_type
3354 ),
3355 ));
3356 }
3357
3358 if let Some(tol) = tolerance {
3359 collect(
3360 check_expression(tol, graph, inferred_types, resolved_types, spec_name),
3361 &mut errors,
3362 );
3363
3364 let tol_type =
3365 infer_expression_type(tol, graph, inferred_types, resolved_types, spec_name);
3366 if !tol_type.is_duration() {
3367 let expr_source = expression
3368 .source_location
3369 .as_ref()
3370 .expect("BUG: expression missing source in check_expression");
3371 errors.push(engine_error_at_graph(
3372 graph,
3373 expr_source,
3374 format!(
3375 "Tolerance in date sugar must be a duration, got type '{}'",
3376 tol_type
3377 ),
3378 ));
3379 }
3380 }
3381 }
3382
3383 ExpressionKind::DateCalendar(_, _, date_expr) => {
3384 collect(
3385 check_expression(date_expr, graph, inferred_types, resolved_types, spec_name),
3386 &mut errors,
3387 );
3388
3389 let date_type =
3390 infer_expression_type(date_expr, graph, inferred_types, resolved_types, spec_name);
3391 if !date_type.is_date() {
3392 let expr_source = expression
3393 .source_location
3394 .as_ref()
3395 .expect("BUG: expression missing source in check_expression");
3396 errors.push(engine_error_at_graph(
3397 graph,
3398 expr_source,
3399 format!(
3400 "Calendar sugar requires a date expression, got type '{}'",
3401 date_type
3402 ),
3403 ));
3404 }
3405 }
3406 }
3407
3408 if errors.is_empty() {
3409 Ok(())
3410 } else {
3411 Err(errors)
3412 }
3413}
3414
3415fn check_rule_types(
3421 graph: &Graph,
3422 execution_order: &[RulePath],
3423 inferred_types: &HashMap<RulePath, LemmaType>,
3424 resolved_types: &ResolvedTypesMap,
3425) -> Result<(), Vec<Error>> {
3426 let mut errors = Vec::new();
3427
3428 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
3429 if let Err(errs) = result {
3430 errors.extend(errs);
3431 }
3432 };
3433
3434 for rule_path in execution_order {
3435 let rule_node = match graph.rules().get(rule_path) {
3436 Some(node) => node,
3437 None => continue,
3438 };
3439 let branches = &rule_node.branches;
3440 let spec_name = rule_node.spec_name.as_str();
3441
3442 if branches.is_empty() {
3443 continue;
3444 }
3445
3446 let (_, default_result) = &branches[0];
3447 collect(
3448 check_expression(
3449 default_result,
3450 graph,
3451 inferred_types,
3452 resolved_types,
3453 spec_name,
3454 ),
3455 &mut errors,
3456 );
3457 let default_type = infer_expression_type(
3458 default_result,
3459 graph,
3460 inferred_types,
3461 resolved_types,
3462 spec_name,
3463 );
3464
3465 let mut non_veto_type: Option<LemmaType> = None;
3466 if !default_type.vetoed() && !default_type.is_undetermined() {
3467 non_veto_type = Some(default_type.clone());
3468 }
3469
3470 for (branch_index, (condition, result)) in branches.iter().enumerate().skip(1) {
3471 if let Some(condition_expression) = condition {
3472 collect(
3473 check_expression(
3474 condition_expression,
3475 graph,
3476 inferred_types,
3477 resolved_types,
3478 spec_name,
3479 ),
3480 &mut errors,
3481 );
3482 let condition_type = infer_expression_type(
3483 condition_expression,
3484 graph,
3485 inferred_types,
3486 resolved_types,
3487 spec_name,
3488 );
3489 if !condition_type.is_boolean() && !condition_type.is_undetermined() {
3490 let condition_source = condition_expression
3491 .source_location
3492 .as_ref()
3493 .expect("BUG: condition expression missing source in check_rule_types");
3494 errors.push(engine_error_at_graph(
3495 graph,
3496 condition_source,
3497 format!(
3498 "Unless clause condition in rule '{}' must be boolean, got {:?}",
3499 rule_path.rule, condition_type
3500 ),
3501 ));
3502 }
3503 }
3504
3505 collect(
3506 check_expression(result, graph, inferred_types, resolved_types, spec_name),
3507 &mut errors,
3508 );
3509 let result_type =
3510 infer_expression_type(result, graph, inferred_types, resolved_types, spec_name);
3511
3512 if !result_type.vetoed() && !result_type.is_undetermined() {
3513 if non_veto_type.is_none() {
3514 non_veto_type = Some(result_type.clone());
3515 } else if let Some(ref existing_type) = non_veto_type {
3516 if !existing_type.has_same_base_type(&result_type) {
3517 let Some(rule_node) = graph.rules().get(rule_path) else {
3518 unreachable!(
3519 "BUG: rule type validation referenced missing rule '{}'",
3520 rule_path.rule
3521 );
3522 };
3523 let rule_source = &rule_node.source;
3524 let default_expr = &branches[0].1;
3525
3526 let mut location_parts = vec![format!(
3527 "{}:{}:{}",
3528 rule_source.attribute, rule_source.span.line, rule_source.span.col
3529 )];
3530
3531 if let Some(loc) = &default_expr.source_location {
3532 location_parts.push(format!(
3533 "default branch at {}:{}:{}",
3534 loc.attribute, loc.span.line, loc.span.col
3535 ));
3536 }
3537 if let Some(loc) = &result.source_location {
3538 location_parts.push(format!(
3539 "unless clause {} at {}:{}:{}",
3540 branch_index, loc.attribute, loc.span.line, loc.span.col
3541 ));
3542 }
3543
3544 errors.push(Error::validation_with_context(
3545 format!("Type mismatch in rule '{}' in spec '{}' ({}): default branch returns {}, but unless clause {} returns {}. All branches must return the same primitive type.",
3546 rule_path.rule,
3547 spec_name,
3548 location_parts.join(", "),
3549 existing_type.name(),
3550 branch_index,
3551 result_type.name()),
3552 Some(rule_source.clone()),
3553 None::<String>,
3554 Some(Arc::clone(&graph.main_spec)),
3555 None,
3556 ));
3557 }
3558 }
3559 }
3560 }
3561 }
3562
3563 if errors.is_empty() {
3564 Ok(())
3565 } else {
3566 Err(errors)
3567 }
3568}
3569
3570fn apply_inferred_types(graph: &mut Graph, inferred_types: HashMap<RulePath, LemmaType>) {
3578 for (rule_path, rule_type) in inferred_types {
3579 if let Some(rule_node) = graph.rules_mut().get_mut(&rule_path) {
3580 rule_node.rule_type = rule_type;
3581 }
3582 }
3583}
3584
3585fn infer_rule_types(
3589 graph: &Graph,
3590 execution_order: &[RulePath],
3591 resolved_types: &ResolvedTypesMap,
3592) -> HashMap<RulePath, LemmaType> {
3593 let mut computed_types: HashMap<RulePath, LemmaType> = HashMap::new();
3594
3595 for rule_path in execution_order {
3596 let rule_node = match graph.rules().get(rule_path) {
3597 Some(node) => node,
3598 None => continue,
3599 };
3600 let branches = &rule_node.branches;
3601 let spec_name = rule_node.spec_name.as_str();
3602
3603 if branches.is_empty() {
3604 continue;
3605 }
3606
3607 let (_, default_result) = &branches[0];
3608 let default_type = infer_expression_type(
3609 default_result,
3610 graph,
3611 &computed_types,
3612 resolved_types,
3613 spec_name,
3614 );
3615
3616 let mut non_veto_type: Option<LemmaType> = None;
3617 if !default_type.vetoed() && !default_type.is_undetermined() {
3618 non_veto_type = Some(default_type.clone());
3619 }
3620
3621 for (_branch_index, (condition, result)) in branches.iter().enumerate().skip(1) {
3622 if let Some(condition_expression) = condition {
3623 let _condition_type = infer_expression_type(
3624 condition_expression,
3625 graph,
3626 &computed_types,
3627 resolved_types,
3628 spec_name,
3629 );
3630 }
3631
3632 let result_type =
3633 infer_expression_type(result, graph, &computed_types, resolved_types, spec_name);
3634 if !result_type.vetoed() && !result_type.is_undetermined() && non_veto_type.is_none() {
3635 non_veto_type = Some(result_type.clone());
3636 }
3637 }
3638
3639 let rule_type = non_veto_type.unwrap_or_else(LemmaType::veto_type);
3640 computed_types.insert(rule_path.clone(), rule_type);
3641 }
3642
3643 computed_types
3644}
3645
3646#[cfg(test)]
3647mod tests {
3648 use super::*;
3649
3650 use crate::parsing::ast::{BooleanValue, Reference, Span, Value};
3651
3652 fn test_source() -> Source {
3653 Source::new(
3654 "test.lemma",
3655 Span {
3656 start: 0,
3657 end: 0,
3658 line: 1,
3659 col: 0,
3660 },
3661 )
3662 }
3663
3664 fn build_graph(main_spec: &LemmaSpec, all_specs: &[LemmaSpec]) -> Result<Graph, Vec<Error>> {
3665 use crate::engine::Context;
3666 use crate::planning::discovery;
3667
3668 let mut ctx = Context::new();
3669 for s in all_specs {
3670 if let Err(e) = ctx.insert_spec(Arc::new(s.clone()), s.from_registry) {
3671 return Err(vec![e]);
3672 }
3673 }
3674 let effective = EffectiveDate::from_option(main_spec.effective_from().cloned());
3675 let main_spec_arc = ctx
3676 .spec_sets()
3677 .get(main_spec.name.as_str())
3678 .and_then(|ss| ss.get_exact(main_spec.effective_from()).cloned())
3679 .expect("main_spec must be in all_specs");
3680 let dag =
3681 discovery::build_dag_for_spec(&ctx, &main_spec_arc, &effective).map_err(
3682 |e| match e {
3683 discovery::DagError::Cycle(es) | discovery::DagError::Other(es) => es,
3684 },
3685 )?;
3686 match Graph::build(&ctx, &main_spec_arc, &dag, &effective) {
3687 Ok((graph, _types)) => Ok(graph),
3688 Err(errors) => Err(errors),
3689 }
3690 }
3691
3692 fn create_test_spec(name: &str) -> LemmaSpec {
3693 LemmaSpec::new(name.to_string())
3694 }
3695
3696 fn create_literal_data(name: &str, value: Value) -> LemmaData {
3697 LemmaData {
3698 reference: Reference {
3699 segments: Vec::new(),
3700 name: name.to_string(),
3701 },
3702 value: ParsedDataValue::Literal(value),
3703 source_location: test_source(),
3704 }
3705 }
3706
3707 fn create_literal_expr(value: Value) -> ast::Expression {
3708 ast::Expression {
3709 kind: ast::ExpressionKind::Literal(value),
3710 source_location: Some(test_source()),
3711 }
3712 }
3713
3714 #[test]
3715 fn should_reject_data_binding_into_non_spec_data() {
3716 let mut spec = create_test_spec("test");
3721 spec = spec.add_data(create_literal_data("x", Value::Number(1.into())));
3722
3723 spec = spec.add_data(LemmaData {
3725 reference: Reference::from_path(vec!["x".to_string(), "y".to_string()]),
3726 value: ParsedDataValue::Literal(Value::Number(2.into())),
3727 source_location: test_source(),
3728 });
3729
3730 let result = build_graph(&spec, &[spec.clone()]);
3731 assert!(
3732 result.is_err(),
3733 "Overriding x.y must fail when x is not a spec reference"
3734 );
3735 }
3736
3737 #[test]
3738 fn should_reject_data_and_rule_name_collision() {
3739 let mut spec = create_test_spec("test");
3744 spec = spec.add_data(create_literal_data("x", Value::Number(1.into())));
3745 spec = spec.add_rule(LemmaRule {
3746 name: "x".to_string(),
3747 expression: create_literal_expr(Value::Number(2.into())),
3748 unless_clauses: Vec::new(),
3749 source_location: test_source(),
3750 });
3751
3752 let result = build_graph(&spec, &[spec.clone()]);
3753 assert!(
3754 result.is_err(),
3755 "Data and rule name collisions should be rejected"
3756 );
3757 }
3758
3759 #[test]
3760 fn test_duplicate_data() {
3761 let mut spec = create_test_spec("test");
3762 spec = spec.add_data(create_literal_data(
3763 "age",
3764 Value::Number(rust_decimal::Decimal::from(25)),
3765 ));
3766 spec = spec.add_data(create_literal_data(
3767 "age",
3768 Value::Number(rust_decimal::Decimal::from(30)),
3769 ));
3770
3771 let result = build_graph(&spec, &[spec.clone()]);
3772 assert!(result.is_err(), "Should detect duplicate data");
3773
3774 let errors = result.unwrap_err();
3775 assert!(errors
3776 .iter()
3777 .any(|e| e.to_string().contains("Duplicate data") && e.to_string().contains("age")));
3778 }
3779
3780 #[test]
3781 fn test_duplicate_rule() {
3782 let mut spec = create_test_spec("test");
3783
3784 let rule1 = LemmaRule {
3785 name: "test_rule".to_string(),
3786 expression: create_literal_expr(Value::Boolean(BooleanValue::True)),
3787 unless_clauses: Vec::new(),
3788 source_location: test_source(),
3789 };
3790 let rule2 = LemmaRule {
3791 name: "test_rule".to_string(),
3792 expression: create_literal_expr(Value::Boolean(BooleanValue::False)),
3793 unless_clauses: Vec::new(),
3794 source_location: test_source(),
3795 };
3796
3797 spec = spec.add_rule(rule1);
3798 spec = spec.add_rule(rule2);
3799
3800 let result = build_graph(&spec, &[spec.clone()]);
3801 assert!(result.is_err(), "Should detect duplicate rule");
3802
3803 let errors = result.unwrap_err();
3804 assert!(errors.iter().any(
3805 |e| e.to_string().contains("Duplicate rule") && e.to_string().contains("test_rule")
3806 ));
3807 }
3808
3809 #[test]
3810 fn test_missing_data_reference() {
3811 let mut spec = create_test_spec("test");
3812
3813 let missing_data_expr = ast::Expression {
3814 kind: ast::ExpressionKind::Reference(Reference {
3815 segments: Vec::new(),
3816 name: "nonexistent".to_string(),
3817 }),
3818 source_location: Some(test_source()),
3819 };
3820
3821 let rule = LemmaRule {
3822 name: "test_rule".to_string(),
3823 expression: missing_data_expr,
3824 unless_clauses: Vec::new(),
3825 source_location: test_source(),
3826 };
3827 spec = spec.add_rule(rule);
3828
3829 let result = build_graph(&spec, &[spec.clone()]);
3830 assert!(result.is_err(), "Should detect missing data");
3831
3832 let errors = result.unwrap_err();
3833 assert!(errors
3834 .iter()
3835 .any(|e| e.to_string().contains("Reference 'nonexistent' not found")));
3836 }
3837
3838 #[test]
3839 fn test_missing_spec_reference() {
3840 let mut spec = create_test_spec("test");
3841
3842 let data = LemmaData {
3843 reference: Reference {
3844 segments: Vec::new(),
3845 name: "contract".to_string(),
3846 },
3847 value: ParsedDataValue::SpecReference(crate::parsing::ast::SpecRef::local(
3848 "nonexistent",
3849 )),
3850 source_location: test_source(),
3851 };
3852 spec = spec.add_data(data);
3853
3854 let result = build_graph(&spec, &[spec.clone()]);
3855 assert!(result.is_err(), "Should detect missing spec");
3856
3857 let errors = result.unwrap_err();
3858 assert!(
3859 errors.iter().any(|e| e.to_string().contains("nonexistent")),
3860 "Error should mention nonexistent spec: {:?}",
3861 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
3862 );
3863 }
3864
3865 #[test]
3866 fn test_data_reference_conversion() {
3867 let mut spec = create_test_spec("test");
3868 spec = spec.add_data(create_literal_data(
3869 "age",
3870 Value::Number(rust_decimal::Decimal::from(25)),
3871 ));
3872
3873 let age_expr = ast::Expression {
3874 kind: ast::ExpressionKind::Reference(Reference {
3875 segments: Vec::new(),
3876 name: "age".to_string(),
3877 }),
3878 source_location: Some(test_source()),
3879 };
3880
3881 let rule = LemmaRule {
3882 name: "test_rule".to_string(),
3883 expression: age_expr,
3884 unless_clauses: Vec::new(),
3885 source_location: test_source(),
3886 };
3887 spec = spec.add_rule(rule);
3888
3889 let result = build_graph(&spec, &[spec.clone()]);
3890 assert!(result.is_ok(), "Should build graph successfully");
3891
3892 let graph = result.unwrap();
3893 let rule_node = graph.rules().values().next().unwrap();
3894
3895 assert!(matches!(
3896 rule_node.branches[0].1.kind,
3897 ExpressionKind::DataPath(_)
3898 ));
3899 }
3900
3901 #[test]
3902 fn test_rule_reference_conversion() {
3903 let mut spec = create_test_spec("test");
3904
3905 let rule1_expr = ast::Expression {
3906 kind: ast::ExpressionKind::Reference(Reference {
3907 segments: Vec::new(),
3908 name: "age".to_string(),
3909 }),
3910 source_location: Some(test_source()),
3911 };
3912
3913 let rule1 = LemmaRule {
3914 name: "rule1".to_string(),
3915 expression: rule1_expr,
3916 unless_clauses: Vec::new(),
3917 source_location: test_source(),
3918 };
3919 spec = spec.add_rule(rule1);
3920
3921 let rule2_expr = ast::Expression {
3922 kind: ast::ExpressionKind::Reference(Reference {
3923 segments: Vec::new(),
3924 name: "rule1".to_string(),
3925 }),
3926 source_location: Some(test_source()),
3927 };
3928
3929 let rule2 = LemmaRule {
3930 name: "rule2".to_string(),
3931 expression: rule2_expr,
3932 unless_clauses: Vec::new(),
3933 source_location: test_source(),
3934 };
3935 spec = spec.add_rule(rule2);
3936
3937 spec = spec.add_data(create_literal_data(
3938 "age",
3939 Value::Number(rust_decimal::Decimal::from(25)),
3940 ));
3941
3942 let result = build_graph(&spec, &[spec.clone()]);
3943 assert!(result.is_ok(), "Should build graph successfully");
3944
3945 let graph = result.unwrap();
3946 let rule2_node = graph
3947 .rules()
3948 .get(&RulePath {
3949 segments: Vec::new(),
3950 rule: "rule2".to_string(),
3951 })
3952 .unwrap();
3953
3954 assert_eq!(rule2_node.depends_on_rules.len(), 1);
3955 assert!(matches!(
3956 rule2_node.branches[0].1.kind,
3957 ExpressionKind::RulePath(_)
3958 ));
3959 }
3960
3961 #[test]
3962 fn test_collect_multiple_errors() {
3963 let mut spec = create_test_spec("test");
3964 spec = spec.add_data(create_literal_data(
3965 "age",
3966 Value::Number(rust_decimal::Decimal::from(25)),
3967 ));
3968 spec = spec.add_data(create_literal_data(
3969 "age",
3970 Value::Number(rust_decimal::Decimal::from(30)),
3971 ));
3972
3973 let missing_data_expr = ast::Expression {
3974 kind: ast::ExpressionKind::Reference(Reference {
3975 segments: Vec::new(),
3976 name: "nonexistent".to_string(),
3977 }),
3978 source_location: Some(test_source()),
3979 };
3980
3981 let rule = LemmaRule {
3982 name: "test_rule".to_string(),
3983 expression: missing_data_expr,
3984 unless_clauses: Vec::new(),
3985 source_location: test_source(),
3986 };
3987 spec = spec.add_rule(rule);
3988
3989 let result = build_graph(&spec, &[spec.clone()]);
3990 assert!(result.is_err(), "Should collect multiple errors");
3991
3992 let errors = result.unwrap_err();
3993 assert!(errors.len() >= 2, "Should have at least 2 errors");
3994 assert!(errors
3995 .iter()
3996 .any(|e| e.to_string().contains("Duplicate data")));
3997 assert!(errors
3998 .iter()
3999 .any(|e| e.to_string().contains("Reference 'nonexistent' not found")));
4000 }
4001
4002 #[test]
4003 fn test_type_registration_collects_multiple_errors() {
4004 use crate::parsing::ast::{DataValue, ParentType, PrimitiveKind, SpecRef};
4005
4006 let type_source = Source::new(
4007 "a.lemma",
4008 Span {
4009 start: 0,
4010 end: 0,
4011 line: 1,
4012 col: 0,
4013 },
4014 );
4015 let spec_a = create_test_spec("spec_a")
4016 .with_attribute("a.lemma".to_string())
4017 .add_data(LemmaData {
4018 reference: Reference::local("dep".to_string()),
4019 value: DataValue::SpecReference(SpecRef::local("spec_b")),
4020 source_location: type_source.clone(),
4021 })
4022 .add_data(LemmaData {
4023 reference: Reference::local("money".to_string()),
4024 value: DataValue::TypeDeclaration {
4025 base: ParentType::Primitive {
4026 primitive: PrimitiveKind::Number,
4027 },
4028 constraints: None,
4029 from: None,
4030 },
4031 source_location: type_source.clone(),
4032 })
4033 .add_data(LemmaData {
4034 reference: Reference::local("money".to_string()),
4035 value: DataValue::TypeDeclaration {
4036 base: ParentType::Primitive {
4037 primitive: PrimitiveKind::Number,
4038 },
4039 constraints: None,
4040 from: None,
4041 },
4042 source_location: type_source,
4043 });
4044
4045 let type_source_b = Source::new(
4046 "b.lemma",
4047 Span {
4048 start: 0,
4049 end: 0,
4050 line: 1,
4051 col: 0,
4052 },
4053 );
4054 let spec_b = create_test_spec("spec_b")
4055 .with_attribute("b.lemma".to_string())
4056 .add_data(LemmaData {
4057 reference: Reference::local("length".to_string()),
4058 value: DataValue::TypeDeclaration {
4059 base: ParentType::Primitive {
4060 primitive: PrimitiveKind::Number,
4061 },
4062 constraints: None,
4063 from: None,
4064 },
4065 source_location: type_source_b.clone(),
4066 })
4067 .add_data(LemmaData {
4068 reference: Reference::local("length".to_string()),
4069 value: DataValue::TypeDeclaration {
4070 base: ParentType::Primitive {
4071 primitive: PrimitiveKind::Number,
4072 },
4073 constraints: None,
4074 from: None,
4075 },
4076 source_location: type_source_b,
4077 });
4078
4079 let mut sources = HashMap::new();
4080 sources.insert(
4081 "a.lemma".to_string(),
4082 "spec spec_a\nwith dep: spec_b\ndata money: number\ndata money: number".to_string(),
4083 );
4084 sources.insert(
4085 "b.lemma".to_string(),
4086 "spec spec_b\ndata length: number\ndata length: number".to_string(),
4087 );
4088
4089 let result = build_graph(&spec_a, &[spec_a.clone(), spec_b.clone()]);
4090 assert!(
4091 result.is_err(),
4092 "Should fail with duplicate type/data errors"
4093 );
4094 }
4095
4096 #[test]
4101 fn spec_ref_resolves_to_single_spec_by_name() {
4102 let code = r#"spec myspec
4103data x: 10
4104
4105spec consumer
4106with m: myspec
4107rule result: m.x"#;
4108 let specs = crate::parse(code, "test.lemma", &crate::ResourceLimits::default())
4109 .unwrap()
4110 .specs;
4111 let consumer = specs.iter().find(|d| d.name == "consumer").unwrap();
4112
4113 let graph = build_graph(consumer, &specs).unwrap();
4114 let data_path = DataPath {
4115 segments: vec![PathSegment {
4116 data: "m".to_string(),
4117 spec: "myspec".to_string(),
4118 }],
4119 data: "x".to_string(),
4120 };
4121 assert!(
4122 graph.data.contains_key(&data_path),
4123 "Ref should resolve to myspec. Data: {:?}",
4124 graph.data.keys().collect::<Vec<_>>()
4125 );
4126 }
4127
4128 #[test]
4129 fn spec_ref_to_nonexistent_spec_is_error() {
4130 let code = r#"spec myspec
4131data x: 10
4132
4133spec consumer
4134with m: nonexistent
4135rule result: m.x"#;
4136 let specs = crate::parse(code, "test.lemma", &crate::ResourceLimits::default())
4137 .unwrap()
4138 .specs;
4139 let consumer = specs.iter().find(|d| d.name == "consumer").unwrap();
4140 let result = build_graph(consumer, &specs);
4141 assert!(result.is_err(), "Should fail for non-existent spec");
4142 }
4143
4144 #[test]
4149 fn self_reference_is_error() {
4150 let code = "spec myspec\nwith m: myspec";
4151 let specs = crate::parse(code, "test.lemma", &crate::ResourceLimits::default())
4152 .unwrap()
4153 .specs;
4154 let result = build_graph(&specs[0], &specs);
4155 assert!(result.is_err(), "Self-reference should be an error");
4156 let errors = result.unwrap_err();
4157 assert!(
4158 errors.iter().any(|e| {
4159 let s = e.to_string();
4160 s.contains("cycle") || s.contains("myspec")
4161 }),
4162 "Error should mention cycle or self-referencing spec: {:?}",
4163 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
4164 );
4165 }
4166}
4167
4168#[derive(Debug, Clone)]
4175pub struct ResolvedSpecTypes {
4176 pub named_types: HashMap<String, LemmaType>,
4178
4179 pub declared_defaults: HashMap<String, ValueKind>,
4184
4185 pub unit_index: HashMap<String, LemmaType>,
4188}
4189
4190#[derive(Debug, Clone, PartialEq)]
4193pub(crate) struct DataTypeDef {
4194 pub parent: ParentType,
4195 pub constraints: Option<Vec<Constraint>>,
4196 pub from: Option<ast::SpecRef>,
4197 pub source: crate::Source,
4198 pub name: String,
4199}
4200
4201#[derive(Debug, Clone)]
4203pub(crate) struct ResolvedParentSpec {
4204 pub spec: Arc<LemmaSpec>,
4205}
4206
4207#[derive(Debug, Clone)]
4212pub(crate) struct TypeResolver<'a> {
4213 data_types: HashMap<Arc<LemmaSpec>, HashMap<String, DataTypeDef>>,
4214 context: &'a Context,
4215 all_registered_specs: Vec<Arc<LemmaSpec>>,
4216}
4217
4218impl<'a> TypeResolver<'a> {
4219 pub fn new(context: &'a Context, _dag: &'a [Arc<LemmaSpec>]) -> Self {
4220 TypeResolver {
4221 data_types: HashMap::new(),
4222 context,
4223 all_registered_specs: Vec::new(),
4224 }
4225 }
4226
4227 pub fn register_all(&mut self, spec: &Arc<LemmaSpec>) -> Vec<Error> {
4229 if !self
4230 .all_registered_specs
4231 .iter()
4232 .any(|s| Arc::ptr_eq(s, spec))
4233 {
4234 self.all_registered_specs.push(Arc::clone(spec));
4235 }
4236
4237 let mut errors = Vec::new();
4238 for data in &spec.data {
4239 if let ParsedDataValue::TypeDeclaration {
4240 base,
4241 constraints,
4242 from,
4243 } = &data.value
4244 {
4245 let name = &data.reference.name;
4246 let ftd = DataTypeDef {
4247 parent: base.clone(),
4248 constraints: constraints.clone(),
4249 from: from.clone(),
4250 source: data.source_location.clone(),
4251 name: name.clone(),
4252 };
4253 if let Err(e) = self.register_type(spec, ftd) {
4254 errors.push(e);
4255 }
4256 }
4257 }
4258 errors
4259 }
4260
4261 pub fn register_type(&mut self, spec: &Arc<LemmaSpec>, def: DataTypeDef) -> Result<(), Error> {
4263 if !self
4264 .all_registered_specs
4265 .iter()
4266 .any(|s| Arc::ptr_eq(s, spec))
4267 {
4268 self.all_registered_specs.push(Arc::clone(spec));
4269 }
4270
4271 let spec_types = self.data_types.entry(Arc::clone(spec)).or_default();
4272 if spec_types.contains_key(&def.name) {
4273 return Err(Error::validation_with_context(
4274 format!(
4275 "Type '{}' is already defined in spec '{}'",
4276 def.name, spec.name
4277 ),
4278 Some(def.source.clone()),
4279 None::<String>,
4280 Some(Arc::clone(spec)),
4281 None,
4282 ));
4283 }
4284 spec_types.insert(def.name.clone(), def);
4285 Ok(())
4286 }
4287
4288 pub fn resolve_and_validate(
4291 &self,
4292 spec: &Arc<LemmaSpec>,
4293 at: &EffectiveDate,
4294 ) -> Result<ResolvedSpecTypes, Vec<Error>> {
4295 let resolved_types = self.resolve_types_internal(spec, at)?;
4296 let mut errors = Vec::new();
4297
4298 for (type_name, lemma_type) in &resolved_types.named_types {
4299 let source = self
4300 .data_types
4301 .get(spec)
4302 .and_then(|defs| defs.get(type_name))
4303 .map(|ftd| ftd.source.clone())
4304 .unwrap_or_else(|| {
4305 unreachable!(
4306 "BUG: resolved type '{}' has no corresponding DataTypeDef in spec '{}'",
4307 type_name, spec.name
4308 )
4309 });
4310 let mut spec_errors = validate_type_specifications(
4311 &lemma_type.specifications,
4312 resolved_types.declared_defaults.get(type_name),
4313 type_name,
4314 &source,
4315 Some(Arc::clone(spec)),
4316 );
4317 errors.append(&mut spec_errors);
4318 }
4319
4320 if errors.is_empty() {
4321 Ok(resolved_types)
4322 } else {
4323 Err(errors)
4324 }
4325 }
4326
4327 fn resolve_types_internal(
4332 &self,
4333 spec: &Arc<LemmaSpec>,
4334 at: &EffectiveDate,
4335 ) -> Result<ResolvedSpecTypes, Vec<Error>> {
4336 let mut named_types = HashMap::new();
4337 let mut declared_defaults: HashMap<String, ValueKind> = HashMap::new();
4338 let mut visited = HashSet::new();
4339
4340 if let Some(spec_types) = self.data_types.get(spec) {
4341 for type_name in spec_types.keys() {
4342 match self.resolve_type_internal(spec, type_name, &mut visited, at) {
4343 Ok(Some((resolved_type, declared_default))) => {
4344 named_types.insert(type_name.clone(), resolved_type);
4345 if let Some(dv) = declared_default {
4346 declared_defaults.insert(type_name.clone(), dv);
4347 }
4348 }
4349 Ok(None) => {
4350 unreachable!(
4351 "BUG: registered type '{}' could not be resolved (spec='{}')",
4352 type_name, spec.name
4353 );
4354 }
4355 Err(es) => return Err(es),
4356 }
4357 visited.clear();
4358 }
4359 }
4360
4361 let mut unit_index_tmp: HashMap<String, (LemmaType, Option<DataTypeDef>)> = HashMap::new();
4363 let mut errors = Vec::new();
4364
4365 let prim_ratio = semantics::primitive_ratio();
4366 for unit in Self::extract_units_from_type(&prim_ratio.specifications) {
4367 unit_index_tmp.insert(unit, (prim_ratio.clone(), None));
4368 }
4369
4370 for (type_name, resolved_type) in &named_types {
4371 let data_type_def = self
4372 .data_types
4373 .get(spec)
4374 .and_then(|defs| defs.get(type_name.as_str()))
4375 .expect("BUG: type was resolved but not in registry");
4376 let e: Result<(), Error> = if resolved_type.is_scale() {
4377 Self::add_scale_units_to_index(
4378 spec,
4379 &mut unit_index_tmp,
4380 resolved_type,
4381 data_type_def,
4382 )
4383 } else if resolved_type.is_ratio() {
4384 Self::add_ratio_units_to_index(
4385 spec,
4386 &mut unit_index_tmp,
4387 resolved_type,
4388 data_type_def,
4389 )
4390 } else {
4391 Ok(())
4392 };
4393 if let Err(e) = e {
4394 errors.push(e);
4395 }
4396 }
4397
4398 if !errors.is_empty() {
4399 return Err(errors);
4400 }
4401
4402 let unit_index = unit_index_tmp
4403 .into_iter()
4404 .map(|(k, (lt, _))| (k, lt))
4405 .collect();
4406
4407 Ok(ResolvedSpecTypes {
4408 named_types,
4409 declared_defaults,
4410 unit_index,
4411 })
4412 }
4413
4414 fn resolve_type_internal(
4415 &self,
4416 spec: &Arc<LemmaSpec>,
4417 name: &str,
4418 visited: &mut HashSet<String>,
4419 at: &EffectiveDate,
4420 ) -> Result<Option<(LemmaType, Option<ValueKind>)>, Vec<Error>> {
4421 let key = format!("{}::{}", spec.name, name);
4422 if visited.contains(&key) {
4423 let source_location = self
4424 .data_types
4425 .get(spec)
4426 .and_then(|dt| dt.get(name))
4427 .map(|ftd| ftd.source.clone())
4428 .unwrap_or_else(|| {
4429 unreachable!(
4430 "BUG: circular dependency detected for type '{}::{}' but type definition not found in registry",
4431 spec.name, name
4432 )
4433 });
4434 return Err(vec![Error::validation_with_context(
4435 format!("Circular dependency detected in type resolution: {}", key),
4436 Some(source_location),
4437 None::<String>,
4438 Some(Arc::clone(spec)),
4439 None,
4440 )]);
4441 }
4442 visited.insert(key.clone());
4443
4444 let ftd = match self.data_types.get(spec).and_then(|dt| dt.get(name)) {
4445 Some(def) => def.clone(),
4446 None => {
4447 visited.remove(&key);
4448 return Ok(None);
4449 }
4450 };
4451
4452 let parent = ftd.parent.clone();
4453 let from = ftd.from.clone();
4454 let constraints = ftd.constraints.clone();
4455
4456 let (parent_specs, parent_declared_default) = match self.resolve_parent(
4457 spec,
4458 &parent,
4459 &from,
4460 visited,
4461 &ftd.source,
4462 at,
4463 ) {
4464 Ok(Some(pair)) => pair,
4465 Ok(None) => {
4466 visited.remove(&key);
4467 return Err(vec![Error::validation_with_context(
4468 format!("Unknown type: '{}'. Type must be defined before use. Valid primitive types are: boolean, scale, number, ratio, text, date, time, duration, percent", parent),
4469 Some(ftd.source.clone()),
4470 None::<String>,
4471 Some(Arc::clone(spec)),
4472 None,
4473 )]);
4474 }
4475 Err(es) => {
4476 visited.remove(&key);
4477 return Err(es);
4478 }
4479 };
4480
4481 let mut declared_default = parent_declared_default;
4482 let final_specs = if let Some(constraints) = &constraints {
4483 match apply_constraints_to_spec(
4484 spec,
4485 parent_specs,
4486 constraints,
4487 &ftd.source,
4488 &mut declared_default,
4489 ) {
4490 Ok(specs) => specs,
4491 Err(errors) => {
4492 visited.remove(&key);
4493 return Err(errors);
4494 }
4495 }
4496 } else {
4497 parent_specs
4498 };
4499
4500 visited.remove(&key);
4501
4502 let extends = {
4503 let parent_name = parent.to_string();
4504 let parent_spec = match self.get_spec_arc_for_parent(spec, &from, &ftd.source, at) {
4505 Ok(x) => x,
4506 Err(e) => return Err(vec![e]),
4507 };
4508 let family = match &parent_spec {
4509 Some(r) => match self.resolve_type_internal(&r.spec, &parent_name, visited, at) {
4510 Ok(Some((parent_type, _))) => parent_type
4511 .scale_family_name()
4512 .map(String::from)
4513 .unwrap_or_else(|| name.to_string()),
4514 Ok(None) => name.to_string(),
4515 Err(es) => return Err(es),
4516 },
4517 None => name.to_string(),
4518 };
4519 let defining_spec = if from.is_some() {
4520 match &parent_spec {
4521 Some(r) => TypeDefiningSpec::Import {
4522 spec: Arc::clone(&r.spec),
4523 },
4524 None => unreachable!(
4525 "BUG: from.is_some() but get_spec_arc_for_parent returned Ok(None)"
4526 ),
4527 }
4528 } else {
4529 TypeDefiningSpec::Local
4530 };
4531 TypeExtends::Custom {
4532 parent: parent_name,
4533 family,
4534 defining_spec,
4535 }
4536 };
4537
4538 Ok(Some((
4539 LemmaType {
4540 name: Some(parent.to_string()),
4541 specifications: final_specs,
4542 extends,
4543 },
4544 declared_default,
4545 )))
4546 }
4547
4548 fn resolve_parent(
4549 &self,
4550 spec: &Arc<LemmaSpec>,
4551 parent: &ParentType,
4552 from: &Option<crate::parsing::ast::SpecRef>,
4553 visited: &mut HashSet<String>,
4554 source: &crate::Source,
4555 at: &EffectiveDate,
4556 ) -> Result<Option<(TypeSpecification, Option<ValueKind>)>, Vec<Error>> {
4557 if let ParentType::Primitive { primitive: kind } = parent {
4558 return Ok(Some((semantics::type_spec_for_primitive(*kind), None)));
4559 }
4560
4561 let parent_name = match parent {
4562 ParentType::Custom { name } => name.as_str(),
4563 ParentType::Primitive { .. } => unreachable!("already returned above"),
4564 };
4565
4566 let parent_spec = match self.get_spec_arc_for_parent(spec, from, source, at) {
4567 Ok(x) => x,
4568 Err(e) => return Err(vec![e]),
4569 };
4570 let result = match &parent_spec {
4571 Some(r) => self.resolve_type_internal(&r.spec, parent_name, visited, at),
4572 None => Ok(None),
4573 };
4574 match result {
4575 Ok(Some((t, declared_default))) => Ok(Some((t.specifications, declared_default))),
4576 Ok(None) => {
4577 let type_exists = parent_spec
4578 .as_ref()
4579 .and_then(|r| self.data_types.get(&r.spec))
4580 .map(|spec_types| spec_types.contains_key(parent_name))
4581 .unwrap_or(false);
4582
4583 if !type_exists {
4584 if from.is_none()
4585 && spec.data.iter().any(|d| {
4586 d.reference.is_local()
4587 && d.reference.name == parent_name
4588 && matches!(&d.value, ParsedDataValue::SpecReference(_))
4589 })
4590 {
4591 return Err(vec![Error::validation_with_context(
4592 format!(
4593 "'{}' is a spec reference and cannot carry a value: a spec reference is not a type and cannot be referenced from a data declaration",
4594 parent_name
4595 ),
4596 Some(source.clone()),
4597 Some(format!(
4598 "To reference data inside the spec, use a dotted path like '{}.<data_name>'",
4599 parent_name
4600 )),
4601 Some(Arc::clone(spec)),
4602 None,
4603 )]);
4604 }
4605 let suggestion = from.as_ref().filter(|r| r.from_registry).map(|r| {
4606 format!(
4607 "Run `lemma get` or `lemma get {}` to fetch this dependency.",
4608 r.name
4609 )
4610 });
4611 Err(vec![Error::validation_with_context(
4612 format!("Unknown type: '{}'. Type must be defined before use. Valid primitive types are: boolean, scale, number, ratio, text, date, time, duration, percent", parent),
4613 Some(source.clone()),
4614 suggestion,
4615 Some(Arc::clone(spec)),
4616 None,
4617 )])
4618 } else {
4619 Ok(None)
4620 }
4621 }
4622 Err(es) => Err(es),
4623 }
4624 }
4625
4626 fn get_spec_arc_for_parent(
4627 &self,
4628 spec: &Arc<LemmaSpec>,
4629 from: &Option<crate::parsing::ast::SpecRef>,
4630 import_site: &crate::Source,
4631 at: &EffectiveDate,
4632 ) -> Result<Option<ResolvedParentSpec>, Error> {
4633 match from {
4634 Some(from_ref) => self
4635 .resolve_spec_for_import(spec, from_ref, import_site, at)
4636 .map(|arc| Some(ResolvedParentSpec { spec: arc })),
4637 None => Ok(Some(ResolvedParentSpec {
4638 spec: Arc::clone(spec),
4639 })),
4640 }
4641 }
4642
4643 fn resolve_spec_for_import(
4644 &self,
4645 spec: &Arc<LemmaSpec>,
4646 from: &crate::parsing::ast::SpecRef,
4647 import_site: &crate::Source,
4648 at: &EffectiveDate,
4649 ) -> Result<Arc<LemmaSpec>, Error> {
4650 discovery::resolve_spec_ref(
4651 self.context,
4652 from,
4653 at,
4654 &spec.name,
4655 Some(import_site.clone()),
4656 Some(Arc::clone(spec)),
4657 )
4658 }
4659
4660 fn add_scale_units_to_index(
4665 spec: &Arc<LemmaSpec>,
4666 unit_index: &mut HashMap<String, (LemmaType, Option<DataTypeDef>)>,
4667 resolved_type: &LemmaType,
4668 defined_by: &DataTypeDef,
4669 ) -> Result<(), Error> {
4670 let units = Self::extract_units_from_type(&resolved_type.specifications);
4671 for unit in units {
4672 if let Some((existing_type, existing_def)) = unit_index.get(&unit) {
4673 let same_type = existing_def.as_ref() == Some(defined_by);
4674
4675 if same_type {
4676 return Err(Error::validation_with_context(
4677 format!(
4678 "Unit '{}' is defined more than once in type '{}'",
4679 unit, defined_by.name
4680 ),
4681 Some(defined_by.source.clone()),
4682 None::<String>,
4683 Some(Arc::clone(spec)),
4684 None,
4685 ));
4686 }
4687
4688 let existing_name: String = existing_def
4689 .as_ref()
4690 .map(|d| d.name.clone())
4691 .unwrap_or_else(|| existing_type.name());
4692 let current_extends_existing = resolved_type
4693 .extends
4694 .parent_name()
4695 .map(|p| p == existing_name.as_str())
4696 .unwrap_or(false);
4697 let existing_extends_current = existing_type
4698 .extends
4699 .parent_name()
4700 .map(|p| p == defined_by.name.as_str())
4701 .unwrap_or(false);
4702
4703 if existing_type.is_scale()
4704 && (current_extends_existing || existing_extends_current)
4705 {
4706 if current_extends_existing {
4707 unit_index.insert(unit, (resolved_type.clone(), Some(defined_by.clone())));
4708 }
4709 continue;
4710 }
4711
4712 if existing_type.same_scale_family(resolved_type) {
4713 continue;
4714 }
4715
4716 return Err(Error::validation_with_context(
4717 format!(
4718 "Ambiguous unit '{}'. Defined in multiple types: '{}' and '{}'",
4719 unit, existing_name, defined_by.name
4720 ),
4721 Some(defined_by.source.clone()),
4722 None::<String>,
4723 Some(Arc::clone(spec)),
4724 None,
4725 ));
4726 }
4727 unit_index.insert(unit, (resolved_type.clone(), Some(defined_by.clone())));
4728 }
4729 Ok(())
4730 }
4731
4732 fn add_ratio_units_to_index(
4733 spec: &Arc<LemmaSpec>,
4734 unit_index: &mut HashMap<String, (LemmaType, Option<DataTypeDef>)>,
4735 resolved_type: &LemmaType,
4736 defined_by: &DataTypeDef,
4737 ) -> Result<(), Error> {
4738 let units = Self::extract_units_from_type(&resolved_type.specifications);
4739 for unit in units {
4740 if let Some((existing_type, existing_def)) = unit_index.get(&unit) {
4741 if existing_type.is_ratio() {
4742 continue;
4743 }
4744 let existing_name: String = existing_def
4745 .as_ref()
4746 .map(|d| d.name.clone())
4747 .unwrap_or_else(|| existing_type.name());
4748 return Err(Error::validation_with_context(
4749 format!(
4750 "Ambiguous unit '{}'. Defined in multiple types: '{}' and '{}'",
4751 unit, existing_name, defined_by.name
4752 ),
4753 Some(defined_by.source.clone()),
4754 None::<String>,
4755 Some(Arc::clone(spec)),
4756 None,
4757 ));
4758 }
4759 unit_index.insert(unit, (resolved_type.clone(), Some(defined_by.clone())));
4760 }
4761 Ok(())
4762 }
4763
4764 fn extract_units_from_type(specs: &TypeSpecification) -> Vec<String> {
4765 match specs {
4766 TypeSpecification::Scale { units, .. } => {
4767 units.iter().map(|unit| unit.name.clone()).collect()
4768 }
4769 TypeSpecification::Ratio { units, .. } => {
4770 units.iter().map(|unit| unit.name.clone()).collect()
4771 }
4772 _ => Vec::new(),
4773 }
4774 }
4775}
4776
4777#[cfg(test)]
4778mod type_resolution_tests {
4779 use super::*;
4780 use crate::parse;
4781 use crate::parsing::ast::{
4782 CommandArg, LemmaSpec, ParentType, PrimitiveKind, TypeConstraintCommand,
4783 };
4784 use crate::ResourceLimits;
4785 use rust_decimal::Decimal;
4786 use std::sync::Arc;
4787
4788 fn test_context_and_effective(
4789 specs: &[Arc<LemmaSpec>],
4790 ) -> (&'static Context, &'static EffectiveDate) {
4791 use crate::engine::Context;
4792 let mut ctx = Context::new();
4793 for s in specs {
4794 ctx.insert_spec(Arc::clone(s), s.from_registry).unwrap();
4795 }
4796 let ctx = Box::leak(Box::new(ctx));
4797 let eff = Box::leak(Box::new(EffectiveDate::Origin));
4798 (ctx, eff)
4799 }
4800
4801 fn dag_and_spec() -> (Vec<Arc<LemmaSpec>>, Arc<LemmaSpec>) {
4802 let spec = LemmaSpec::new("test_spec".to_string());
4803 let arc = Arc::new(spec);
4804 let dag = vec![Arc::clone(&arc)];
4805 (dag, arc)
4806 }
4807
4808 fn resolver_for_code(code: &str) -> (TypeResolver<'static>, Vec<Arc<LemmaSpec>>) {
4809 let specs = parse(code, "test.lemma", &ResourceLimits::default())
4810 .unwrap()
4811 .specs;
4812 let spec_arcs: Vec<Arc<LemmaSpec>> = specs.iter().map(|s| Arc::new(s.clone())).collect();
4813 let dag: Vec<Arc<LemmaSpec>> = spec_arcs.iter().map(Arc::clone).collect();
4814 let dag = Box::leak(Box::new(dag));
4815 let (ctx, _) = test_context_and_effective(&spec_arcs);
4816 let mut resolver = TypeResolver::new(ctx, dag);
4817 for spec_arc in &spec_arcs {
4818 resolver.register_all(spec_arc);
4819 }
4820 (resolver, spec_arcs)
4821 }
4822
4823 fn resolver_single_spec(code: &str) -> (TypeResolver<'static>, Arc<LemmaSpec>) {
4824 let (resolver, spec_arcs) = resolver_for_code(code);
4825 let spec_arc = spec_arcs.into_iter().next().expect("at least one spec");
4826 (resolver, spec_arc)
4827 }
4828
4829 #[test]
4830 fn test_type_spec_for_primitive_covers_all_variants() {
4831 use crate::parsing::ast::PrimitiveKind;
4832 use crate::planning::semantics::type_spec_for_primitive;
4833
4834 for kind in [
4835 PrimitiveKind::Boolean,
4836 PrimitiveKind::Scale,
4837 PrimitiveKind::Number,
4838 PrimitiveKind::Percent,
4839 PrimitiveKind::Ratio,
4840 PrimitiveKind::Text,
4841 PrimitiveKind::Date,
4842 PrimitiveKind::Time,
4843 PrimitiveKind::Duration,
4844 ] {
4845 let spec = type_spec_for_primitive(kind);
4846 assert!(
4847 !matches!(
4848 spec,
4849 crate::planning::semantics::TypeSpecification::Undetermined
4850 ),
4851 "type_spec_for_primitive({:?}) returned Undetermined",
4852 kind
4853 );
4854 }
4855 }
4856
4857 #[test]
4858 fn test_register_data_type_def() {
4859 let (dag, spec_arc) = dag_and_spec();
4860 let (ctx, _) = test_context_and_effective(&dag);
4861 let mut resolver = TypeResolver::new(ctx, &dag);
4862 let ftd = DataTypeDef {
4863 parent: ParentType::Primitive {
4864 primitive: PrimitiveKind::Number,
4865 },
4866 constraints: Some(vec![
4867 (
4868 TypeConstraintCommand::Minimum,
4869 vec![CommandArg::Literal(crate::literals::Value::Number(
4870 Decimal::ZERO,
4871 ))],
4872 ),
4873 (
4874 TypeConstraintCommand::Maximum,
4875 vec![CommandArg::Literal(crate::literals::Value::Number(
4876 Decimal::from(150),
4877 ))],
4878 ),
4879 ]),
4880 from: None,
4881 source: crate::Source::new(
4882 "<test>",
4883 crate::parsing::ast::Span {
4884 start: 0,
4885 end: 0,
4886 line: 1,
4887 col: 0,
4888 },
4889 ),
4890 name: "age".to_string(),
4891 };
4892
4893 let result = resolver.register_type(&spec_arc, ftd);
4894 assert!(result.is_ok());
4895 let resolved = resolver
4896 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
4897 .unwrap();
4898 assert!(resolved.named_types.contains_key("age"));
4899 }
4900
4901 #[test]
4902 fn test_register_duplicate_type_fails() {
4903 let (dag, spec_arc) = dag_and_spec();
4904 let (ctx, _) = test_context_and_effective(&dag);
4905 let mut resolver = TypeResolver::new(ctx, &dag);
4906 let ftd = DataTypeDef {
4907 parent: ParentType::Primitive {
4908 primitive: PrimitiveKind::Number,
4909 },
4910 constraints: None,
4911 from: None,
4912 source: crate::Source::new(
4913 "<test>",
4914 crate::parsing::ast::Span {
4915 start: 0,
4916 end: 0,
4917 line: 1,
4918 col: 0,
4919 },
4920 ),
4921 name: "money".to_string(),
4922 };
4923
4924 resolver.register_type(&spec_arc, ftd.clone()).unwrap();
4925 let result = resolver.register_type(&spec_arc, ftd);
4926 assert!(result.is_err());
4927 }
4928
4929 #[test]
4930 fn test_resolve_custom_type_from_primitive() {
4931 let (dag, spec_arc) = dag_and_spec();
4932 let (ctx, _) = test_context_and_effective(&dag);
4933 let mut resolver = TypeResolver::new(ctx, &dag);
4934 let ftd = DataTypeDef {
4935 parent: ParentType::Primitive {
4936 primitive: PrimitiveKind::Number,
4937 },
4938 constraints: None,
4939 from: None,
4940 source: crate::Source::new(
4941 "<test>",
4942 crate::parsing::ast::Span {
4943 start: 0,
4944 end: 0,
4945 line: 1,
4946 col: 0,
4947 },
4948 ),
4949 name: "money".to_string(),
4950 };
4951
4952 resolver.register_type(&spec_arc, ftd).unwrap();
4953 let resolved = resolver
4954 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
4955 .unwrap();
4956
4957 assert!(resolved.named_types.contains_key("money"));
4958 let money_type = resolved.named_types.get("money").unwrap();
4959 assert_eq!(money_type.name, Some("number".to_string()));
4960 }
4961
4962 #[test]
4963 fn test_type_definition_resolution() {
4964 let (resolver, spec_arc) = resolver_single_spec(
4965 r#"spec test
4966data dice: number -> minimum 0 -> maximum 6"#,
4967 );
4968
4969 let resolved_types = resolver
4970 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
4971 .unwrap();
4972 let dice_type = resolved_types.named_types.get("dice").unwrap();
4973
4974 match &dice_type.specifications {
4975 TypeSpecification::Number {
4976 minimum, maximum, ..
4977 } => {
4978 assert_eq!(*minimum, Some(Decimal::from(0)));
4979 assert_eq!(*maximum, Some(Decimal::from(6)));
4980 }
4981 _ => panic!("Expected Number type specifications"),
4982 }
4983 }
4984
4985 #[test]
4986 fn test_type_definition_with_multiple_commands() {
4987 let (resolver, spec_arc) = resolver_single_spec(
4988 r#"spec test
4989data money: scale -> decimals 2 -> unit eur 1.0 -> unit usd 1.18"#,
4990 );
4991
4992 let resolved_types = resolver
4993 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
4994 .unwrap();
4995 let money_type = resolved_types.named_types.get("money").unwrap();
4996
4997 match &money_type.specifications {
4998 TypeSpecification::Scale {
4999 decimals, units, ..
5000 } => {
5001 assert_eq!(*decimals, Some(2));
5002 assert_eq!(units.len(), 2);
5003 assert!(units.iter().any(|u| u.name == "eur"));
5004 assert!(units.iter().any(|u| u.name == "usd"));
5005 }
5006 _ => panic!("Expected Scale type specifications"),
5007 }
5008 }
5009
5010 #[test]
5011 fn test_number_type_with_decimals() {
5012 let (resolver, spec_arc) = resolver_single_spec(
5013 r#"spec test
5014data price: number -> decimals 2 -> minimum 0"#,
5015 );
5016
5017 let resolved_types = resolver
5018 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
5019 .unwrap();
5020 let price_type = resolved_types.named_types.get("price").unwrap();
5021
5022 match &price_type.specifications {
5023 TypeSpecification::Number {
5024 decimals, minimum, ..
5025 } => {
5026 assert_eq!(*decimals, Some(2));
5027 assert_eq!(*minimum, Some(Decimal::from(0)));
5028 }
5029 _ => panic!("Expected Number type specifications with decimals"),
5030 }
5031 }
5032
5033 #[test]
5034 fn test_number_type_decimals_only() {
5035 let (resolver, spec_arc) = resolver_single_spec(
5036 r#"spec test
5037data precise_number: number -> decimals 4"#,
5038 );
5039
5040 let resolved_types = resolver
5041 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
5042 .unwrap();
5043 let precise_type = resolved_types.named_types.get("precise_number").unwrap();
5044
5045 match &precise_type.specifications {
5046 TypeSpecification::Number { decimals, .. } => {
5047 assert_eq!(*decimals, Some(4));
5048 }
5049 _ => panic!("Expected Number type with decimals 4"),
5050 }
5051 }
5052
5053 #[test]
5054 fn test_scale_type_decimals_only() {
5055 let (resolver, spec_arc) = resolver_single_spec(
5056 r#"spec test
5057data weight: scale -> unit kg 1 -> decimals 3"#,
5058 );
5059
5060 let resolved_types = resolver
5061 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
5062 .unwrap();
5063 let weight_type = resolved_types.named_types.get("weight").unwrap();
5064
5065 match &weight_type.specifications {
5066 TypeSpecification::Scale { decimals, .. } => {
5067 assert_eq!(*decimals, Some(3));
5068 }
5069 _ => panic!("Expected Scale type with decimals 3"),
5070 }
5071 }
5072
5073 #[test]
5074 fn test_ratio_type_accepts_optional_decimals_command() {
5075 let (resolver, spec_arc) = resolver_single_spec(
5076 r#"spec test
5077data ratio_type: ratio -> decimals 2"#,
5078 );
5079
5080 let resolved_types = resolver
5081 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
5082 .unwrap();
5083 let ratio_type = resolved_types.named_types.get("ratio_type").unwrap();
5084
5085 match &ratio_type.specifications {
5086 TypeSpecification::Ratio { decimals, .. } => {
5087 assert_eq!(
5088 *decimals,
5089 Some(2),
5090 "ratio type should accept decimals command"
5091 );
5092 }
5093 _ => panic!("Expected Ratio type with decimals 2"),
5094 }
5095 }
5096
5097 #[test]
5098 fn test_ratio_type_with_default_command() {
5099 let (resolver, spec_arc) = resolver_single_spec(
5100 r#"spec test
5101data percentage: ratio -> minimum 0 -> maximum 1 -> default 0.5"#,
5102 );
5103
5104 let resolved_types = resolver
5105 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
5106 .unwrap();
5107 let percentage_type = resolved_types.named_types.get("percentage").unwrap();
5108
5109 match &percentage_type.specifications {
5110 TypeSpecification::Ratio {
5111 minimum, maximum, ..
5112 } => {
5113 assert_eq!(
5114 *minimum,
5115 Some(Decimal::from(0)),
5116 "ratio type should have minimum 0"
5117 );
5118 assert_eq!(
5119 *maximum,
5120 Some(Decimal::from(1)),
5121 "ratio type should have maximum 1"
5122 );
5123 }
5124 _ => panic!("Expected Ratio type with minimum and maximum"),
5125 }
5126
5127 let declared = resolved_types
5128 .declared_defaults
5129 .get("percentage")
5130 .expect("declared default must be tracked for percentage");
5131 match declared {
5132 ValueKind::Ratio(v, _) => assert_eq!(*v, Decimal::from_i128_with_scale(5, 1)),
5133 other => panic!("expected Ratio declared default, got {:?}", other),
5134 }
5135 }
5136
5137 #[test]
5138 fn test_scale_extension_chain_same_family_units_allowed() {
5139 let (resolver, spec_arc) = resolver_single_spec(
5140 r#"spec test
5141data money: scale -> unit eur 1
5142data money2: money -> unit usd 1.24"#,
5143 );
5144
5145 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5146 assert!(
5147 result.is_ok(),
5148 "Scale extension chain should resolve: {:?}",
5149 result.err()
5150 );
5151
5152 let resolved = result.unwrap();
5153 assert!(
5154 resolved.unit_index.contains_key("eur"),
5155 "eur should be in unit_index"
5156 );
5157 assert!(
5158 resolved.unit_index.contains_key("usd"),
5159 "usd should be in unit_index"
5160 );
5161 let eur_type = resolved.unit_index.get("eur").unwrap();
5162 let usd_type = resolved.unit_index.get("usd").unwrap();
5163 assert_eq!(
5164 eur_type.name.as_deref(),
5165 Some("money"),
5166 "more derived type (money2) should own eur; its parent name is 'money'"
5167 );
5168 assert_eq!(
5169 usd_type.name.as_deref(),
5170 Some("money"),
5171 "usd defined on money2 whose parent is 'money'"
5172 );
5173 }
5174
5175 #[test]
5176 fn test_invalid_parent_type_in_named_type_should_error() {
5177 let (resolver, spec_arc) = resolver_single_spec(
5178 r#"spec test
5179data invalid: nonexistent_type -> minimum 0"#,
5180 );
5181
5182 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5183 assert!(result.is_err(), "Should reject invalid parent type");
5184
5185 let errs = result.unwrap_err();
5186 assert!(!errs.is_empty(), "expected at least one error");
5187 let error_msg = errs[0].to_string();
5188 assert!(
5189 error_msg.contains("Unknown type") && error_msg.contains("nonexistent_type"),
5190 "Error should mention unknown type. Got: {}",
5191 error_msg
5192 );
5193 }
5194
5195 #[test]
5196 fn test_invalid_primitive_type_name_should_error() {
5197 let (resolver, spec_arc) = resolver_single_spec(
5198 r#"spec test
5199data invalid: choice -> option "a""#,
5200 );
5201
5202 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5203 assert!(result.is_err(), "Should reject invalid type base 'choice'");
5204
5205 let errs = result.unwrap_err();
5206 assert!(!errs.is_empty(), "expected at least one error");
5207 let error_msg = errs[0].to_string();
5208 assert!(
5209 error_msg.contains("Unknown type") && error_msg.contains("choice"),
5210 "Error should mention unknown type 'choice'. Got: {}",
5211 error_msg
5212 );
5213 }
5214
5215 #[test]
5216 fn test_unit_constraint_validation_errors_are_reported() {
5217 let (resolver, spec_arc) = resolver_single_spec(
5218 r#"spec test
5219data money: scale
5220 -> unit eur 1.00
5221 -> unit usd 1.19
5222
5223data money2: money
5224 -> unit eur 1.20
5225 -> unit usd 1.21
5226 -> unit gbp 1.30"#,
5227 );
5228
5229 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5230 assert!(
5231 result.is_err(),
5232 "Expected unit constraint conflicts to error"
5233 );
5234
5235 let errs = result.unwrap_err();
5236 assert!(!errs.is_empty(), "expected at least one error");
5237 let error_msg = errs
5238 .iter()
5239 .map(ToString::to_string)
5240 .collect::<Vec<_>>()
5241 .join("; ");
5242 assert!(
5243 error_msg.contains("eur") || error_msg.contains("usd"),
5244 "Error should mention the conflicting units. Got: {}",
5245 error_msg
5246 );
5247 }
5248
5249 #[test]
5250 fn test_spec_level_unit_ambiguity_errors_are_reported() {
5251 let (resolver, spec_arc) = resolver_single_spec(
5252 r#"spec test
5253data money_a: scale
5254 -> unit eur 1.00
5255 -> unit usd 1.19
5256
5257data money_b: scale
5258 -> unit eur 1.00
5259 -> unit usd 1.20
5260
5261data length_a: scale
5262 -> unit meter 1.0
5263
5264data length_b: scale
5265 -> unit meter 1.0"#,
5266 );
5267
5268 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5269 assert!(
5270 result.is_err(),
5271 "Expected ambiguous unit definitions to error"
5272 );
5273
5274 let errs = result.unwrap_err();
5275 assert!(!errs.is_empty(), "expected at least one error");
5276 let error_msg = errs
5277 .iter()
5278 .map(ToString::to_string)
5279 .collect::<Vec<_>>()
5280 .join("; ");
5281 assert!(
5282 error_msg.contains("eur") || error_msg.contains("usd") || error_msg.contains("meter"),
5283 "Error should mention at least one ambiguous unit. Got: {}",
5284 error_msg
5285 );
5286 }
5287
5288 #[test]
5289 fn test_number_type_cannot_have_units() {
5290 let (resolver, spec_arc) = resolver_single_spec(
5291 r#"spec test
5292data price: number
5293 -> unit eur 1.00"#,
5294 );
5295
5296 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5297 assert!(result.is_err(), "Number types must reject unit commands");
5298
5299 let errs = result.unwrap_err();
5300 assert!(!errs.is_empty(), "expected at least one error");
5301 let error_msg = errs[0].to_string();
5302 assert!(
5303 error_msg.contains("unit") && error_msg.contains("number"),
5304 "Error should mention units are invalid on number. Got: {}",
5305 error_msg
5306 );
5307 }
5308
5309 #[test]
5310 fn test_extending_type_inherits_units() {
5311 let (resolver, spec_arc) = resolver_single_spec(
5312 r#"spec test
5313data money: scale
5314 -> unit eur 1.00
5315 -> unit usd 1.19
5316
5317data my_money: money
5318 -> unit gbp 1.30"#,
5319 );
5320
5321 let resolved = resolver
5322 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
5323 .unwrap();
5324 let my_money_type = resolved.named_types.get("my_money").unwrap();
5325
5326 match &my_money_type.specifications {
5327 TypeSpecification::Scale { units, .. } => {
5328 assert_eq!(units.len(), 3);
5329 assert!(units.iter().any(|u| u.name == "eur"));
5330 assert!(units.iter().any(|u| u.name == "usd"));
5331 assert!(units.iter().any(|u| u.name == "gbp"));
5332 }
5333 other => panic!("Expected Scale type specifications, got {:?}", other),
5334 }
5335 }
5336
5337 #[test]
5338 fn test_duplicate_unit_in_same_type_is_rejected() {
5339 let (resolver, spec_arc) = resolver_single_spec(
5340 r#"spec test
5341data money: scale
5342 -> unit eur 1.00
5343 -> unit eur 1.19"#,
5344 );
5345
5346 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
5347 assert!(
5348 result.is_err(),
5349 "Duplicate units within a type should error"
5350 );
5351
5352 let errs = result.unwrap_err();
5353 assert!(!errs.is_empty(), "expected at least one error");
5354 let error_msg = errs[0].to_string();
5355 assert!(
5356 error_msg.contains("Duplicate unit")
5357 || error_msg.contains("duplicate")
5358 || error_msg.contains("already exists")
5359 || error_msg.contains("eur"),
5360 "Error should mention duplicate unit issue. Got: {}",
5361 error_msg
5362 );
5363 }
5364}
5365
5366pub fn validate_type_specifications(
5380 specs: &TypeSpecification,
5381 declared_default: Option<&ValueKind>,
5382 type_name: &str,
5383 source: &Source,
5384 spec_context: Option<Arc<LemmaSpec>>,
5385) -> Vec<Error> {
5386 let mut errors = Vec::new();
5387
5388 match specs {
5389 TypeSpecification::Scale {
5390 minimum,
5391 maximum,
5392 decimals,
5393 precision,
5394 units,
5395 ..
5396 } => {
5397 if let (Some(min), Some(max)) = (minimum, maximum) {
5399 if min > max {
5400 errors.push(Error::validation_with_context(
5401 format!(
5402 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
5403 type_name, min, max
5404 ),
5405 Some(source.clone()),
5406 None::<String>,
5407 spec_context.clone(),
5408 None,
5409 ));
5410 }
5411 }
5412
5413 if let Some(d) = decimals {
5415 if *d > 28 {
5416 errors.push(Error::validation_with_context(
5417 format!(
5418 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
5419 type_name, d
5420 ),
5421 Some(source.clone()),
5422 None::<String>,
5423 spec_context.clone(),
5424 None,
5425 ));
5426 }
5427 }
5428
5429 if let Some(prec) = precision {
5431 if *prec <= Decimal::ZERO {
5432 errors.push(Error::validation_with_context(
5433 format!(
5434 "Type '{}' has invalid precision: {}. Must be positive",
5435 type_name, prec
5436 ),
5437 Some(source.clone()),
5438 None::<String>,
5439 spec_context.clone(),
5440 None,
5441 ));
5442 }
5443 }
5444
5445 if let Some(ValueKind::Scale(def_value, def_unit)) = declared_default {
5446 if !units.iter().any(|u| u.name == *def_unit) {
5447 errors.push(Error::validation_with_context(
5448 format!(
5449 "Type '{}' default unit '{}' is not a valid unit. Valid units: {}",
5450 type_name,
5451 def_unit,
5452 units
5453 .iter()
5454 .map(|u| u.name.clone())
5455 .collect::<Vec<_>>()
5456 .join(", ")
5457 ),
5458 Some(source.clone()),
5459 None::<String>,
5460 spec_context.clone(),
5461 None,
5462 ));
5463 }
5464 if let Some(min) = minimum {
5465 if *def_value < *min {
5466 errors.push(Error::validation_with_context(
5467 format!(
5468 "Type '{}' default value {} {} is less than minimum {}",
5469 type_name, def_value, def_unit, min
5470 ),
5471 Some(source.clone()),
5472 None::<String>,
5473 spec_context.clone(),
5474 None,
5475 ));
5476 }
5477 }
5478 if let Some(max) = maximum {
5479 if *def_value > *max {
5480 errors.push(Error::validation_with_context(
5481 format!(
5482 "Type '{}' default value {} {} is greater than maximum {}",
5483 type_name, def_value, def_unit, max
5484 ),
5485 Some(source.clone()),
5486 None::<String>,
5487 spec_context.clone(),
5488 None,
5489 ));
5490 }
5491 }
5492 }
5493
5494 if units.is_empty() {
5496 errors.push(Error::validation_with_context(
5497 format!(
5498 "Type '{}' is a scale type but has no units. Scale types must define at least one unit (e.g. -> unit eur 1).",
5499 type_name
5500 ),
5501 Some(source.clone()),
5502 None::<String>,
5503 spec_context.clone(),
5504 None,
5505 ));
5506 }
5507
5508 if !units.is_empty() {
5510 let mut seen_names: Vec<String> = Vec::new();
5511 for unit in units.iter() {
5512 if unit.name.trim().is_empty() {
5514 errors.push(Error::validation_with_context(
5515 format!(
5516 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
5517 type_name
5518 ),
5519 Some(source.clone()),
5520 None::<String>,
5521 spec_context.clone(),
5522 None,
5523 ));
5524 }
5525
5526 let lower_name = unit.name.to_lowercase();
5528 if seen_names
5529 .iter()
5530 .any(|seen| seen.to_lowercase() == lower_name)
5531 {
5532 errors.push(Error::validation_with_context(
5533 format!("Type '{}' has duplicate unit name '{}' (case-insensitive). Unit names must be unique within a type.", type_name, unit.name),
5534 Some(source.clone()),
5535 None::<String>,
5536 spec_context.clone(),
5537 None,
5538 ));
5539 } else {
5540 seen_names.push(unit.name.clone());
5541 }
5542
5543 if unit.value <= Decimal::ZERO {
5545 errors.push(Error::validation_with_context(
5546 format!("Type '{}' has unit '{}' with invalid value {}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, unit.value),
5547 Some(source.clone()),
5548 None::<String>,
5549 spec_context.clone(),
5550 None,
5551 ));
5552 }
5553 }
5554 }
5555 }
5556 TypeSpecification::Number {
5557 minimum,
5558 maximum,
5559 decimals,
5560 precision,
5561 ..
5562 } => {
5563 if let (Some(min), Some(max)) = (minimum, maximum) {
5565 if min > max {
5566 errors.push(Error::validation_with_context(
5567 format!(
5568 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
5569 type_name, min, max
5570 ),
5571 Some(source.clone()),
5572 None::<String>,
5573 spec_context.clone(),
5574 None,
5575 ));
5576 }
5577 }
5578
5579 if let Some(d) = decimals {
5581 if *d > 28 {
5582 errors.push(Error::validation_with_context(
5583 format!(
5584 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
5585 type_name, d
5586 ),
5587 Some(source.clone()),
5588 None::<String>,
5589 spec_context.clone(),
5590 None,
5591 ));
5592 }
5593 }
5594
5595 if let Some(prec) = precision {
5597 if *prec <= Decimal::ZERO {
5598 errors.push(Error::validation_with_context(
5599 format!(
5600 "Type '{}' has invalid precision: {}. Must be positive",
5601 type_name, prec
5602 ),
5603 Some(source.clone()),
5604 None::<String>,
5605 spec_context.clone(),
5606 None,
5607 ));
5608 }
5609 }
5610
5611 if let Some(ValueKind::Number(def)) = declared_default {
5612 if let Some(min) = minimum {
5613 if *def < *min {
5614 errors.push(Error::validation_with_context(
5615 format!(
5616 "Type '{}' default value {} is less than minimum {}",
5617 type_name, def, min
5618 ),
5619 Some(source.clone()),
5620 None::<String>,
5621 spec_context.clone(),
5622 None,
5623 ));
5624 }
5625 }
5626 if let Some(max) = maximum {
5627 if *def > *max {
5628 errors.push(Error::validation_with_context(
5629 format!(
5630 "Type '{}' default value {} is greater than maximum {}",
5631 type_name, def, max
5632 ),
5633 Some(source.clone()),
5634 None::<String>,
5635 spec_context.clone(),
5636 None,
5637 ));
5638 }
5639 }
5640 }
5641 }
5643
5644 TypeSpecification::Ratio {
5645 minimum,
5646 maximum,
5647 decimals,
5648 units,
5649 ..
5650 } => {
5651 if let Some(d) = decimals {
5653 if *d > 28 {
5654 errors.push(Error::validation_with_context(
5655 format!(
5656 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
5657 type_name, d
5658 ),
5659 Some(source.clone()),
5660 None::<String>,
5661 spec_context.clone(),
5662 None,
5663 ));
5664 }
5665 }
5666
5667 if let (Some(min), Some(max)) = (minimum, maximum) {
5669 if min > max {
5670 errors.push(Error::validation_with_context(
5671 format!(
5672 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
5673 type_name, min, max
5674 ),
5675 Some(source.clone()),
5676 None::<String>,
5677 spec_context.clone(),
5678 None,
5679 ));
5680 }
5681 }
5682
5683 if let Some(ValueKind::Ratio(def, _)) = declared_default {
5684 if let Some(min) = minimum {
5685 if *def < *min {
5686 errors.push(Error::validation_with_context(
5687 format!(
5688 "Type '{}' default value {} is less than minimum {}",
5689 type_name, def, min
5690 ),
5691 Some(source.clone()),
5692 None::<String>,
5693 spec_context.clone(),
5694 None,
5695 ));
5696 }
5697 }
5698 if let Some(max) = maximum {
5699 if *def > *max {
5700 errors.push(Error::validation_with_context(
5701 format!(
5702 "Type '{}' default value {} is greater than maximum {}",
5703 type_name, def, max
5704 ),
5705 Some(source.clone()),
5706 None::<String>,
5707 spec_context.clone(),
5708 None,
5709 ));
5710 }
5711 }
5712 }
5713
5714 if !units.is_empty() {
5718 let mut seen_names: Vec<String> = Vec::new();
5719 for unit in units.iter() {
5720 if unit.name.trim().is_empty() {
5722 errors.push(Error::validation_with_context(
5723 format!(
5724 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
5725 type_name
5726 ),
5727 Some(source.clone()),
5728 None::<String>,
5729 spec_context.clone(),
5730 None,
5731 ));
5732 }
5733
5734 let lower_name = unit.name.to_lowercase();
5736 if seen_names
5737 .iter()
5738 .any(|seen| seen.to_lowercase() == lower_name)
5739 {
5740 errors.push(Error::validation_with_context(
5741 format!("Type '{}' has duplicate unit name '{}' (case-insensitive). Unit names must be unique within a type.", type_name, unit.name),
5742 Some(source.clone()),
5743 None::<String>,
5744 spec_context.clone(),
5745 None,
5746 ));
5747 } else {
5748 seen_names.push(unit.name.clone());
5749 }
5750
5751 if unit.value <= Decimal::ZERO {
5753 errors.push(Error::validation_with_context(
5754 format!("Type '{}' has unit '{}' with invalid value {}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, unit.value),
5755 Some(source.clone()),
5756 None::<String>,
5757 spec_context.clone(),
5758 None,
5759 ));
5760 }
5761 }
5762 }
5763 }
5764
5765 TypeSpecification::Text {
5766 length, options, ..
5767 } => {
5768 if let Some(ValueKind::Text(def)) = declared_default {
5769 let def_len = def.len();
5770
5771 if let Some(len) = length {
5772 if def_len != *len {
5773 errors.push(Error::validation_with_context(
5774 format!("Type '{}' default value length {} does not match required length {}", type_name, def_len, len),
5775 Some(source.clone()),
5776 None::<String>,
5777 spec_context.clone(),
5778 None,
5779 ));
5780 }
5781 }
5782 if !options.is_empty() && !options.contains(def) {
5783 errors.push(Error::validation_with_context(
5784 format!(
5785 "Type '{}' default value '{}' is not in allowed options: {:?}",
5786 type_name, def, options
5787 ),
5788 Some(source.clone()),
5789 None::<String>,
5790 spec_context.clone(),
5791 None,
5792 ));
5793 }
5794 }
5795 }
5796
5797 TypeSpecification::Date {
5798 minimum,
5799 maximum,
5800 ..
5801 } => {
5802 if let (Some(min), Some(max)) = (minimum, maximum) {
5804 let min_sem = semantics::date_time_to_semantic(min);
5805 let max_sem = semantics::date_time_to_semantic(max);
5806 if semantics::compare_semantic_dates(&min_sem, &max_sem) == Ordering::Greater {
5807 errors.push(Error::validation_with_context(
5808 format!(
5809 "Type '{}' has invalid date range: minimum {} is after maximum {}",
5810 type_name, min, max
5811 ),
5812 Some(source.clone()),
5813 None::<String>,
5814 spec_context.clone(),
5815 None,
5816 ));
5817 }
5818 }
5819
5820 if let Some(ValueKind::Date(def)) = declared_default {
5821 if let Some(min) = minimum {
5822 let min_sem = semantics::date_time_to_semantic(min);
5823 if semantics::compare_semantic_dates(def, &min_sem) == Ordering::Less {
5824 errors.push(Error::validation_with_context(
5825 format!(
5826 "Type '{}' default date {} is before minimum {}",
5827 type_name, def, min
5828 ),
5829 Some(source.clone()),
5830 None::<String>,
5831 spec_context.clone(),
5832 None,
5833 ));
5834 }
5835 }
5836 if let Some(max) = maximum {
5837 let max_sem = semantics::date_time_to_semantic(max);
5838 if semantics::compare_semantic_dates(def, &max_sem) == Ordering::Greater {
5839 errors.push(Error::validation_with_context(
5840 format!(
5841 "Type '{}' default date {} is after maximum {}",
5842 type_name, def, max
5843 ),
5844 Some(source.clone()),
5845 None::<String>,
5846 spec_context.clone(),
5847 None,
5848 ));
5849 }
5850 }
5851 }
5852 }
5853
5854 TypeSpecification::Time {
5855 minimum,
5856 maximum,
5857 ..
5858 } => {
5859 if let (Some(min), Some(max)) = (minimum, maximum) {
5861 let min_sem = semantics::time_to_semantic(min);
5862 let max_sem = semantics::time_to_semantic(max);
5863 if semantics::compare_semantic_times(&min_sem, &max_sem) == Ordering::Greater {
5864 errors.push(Error::validation_with_context(
5865 format!(
5866 "Type '{}' has invalid time range: minimum {} is after maximum {}",
5867 type_name, min, max
5868 ),
5869 Some(source.clone()),
5870 None::<String>,
5871 spec_context.clone(),
5872 None,
5873 ));
5874 }
5875 }
5876
5877 if let Some(ValueKind::Time(def)) = declared_default {
5878 if let Some(min) = minimum {
5879 let min_sem = semantics::time_to_semantic(min);
5880 if semantics::compare_semantic_times(def, &min_sem) == Ordering::Less {
5881 errors.push(Error::validation_with_context(
5882 format!(
5883 "Type '{}' default time {} is before minimum {}",
5884 type_name, def, min
5885 ),
5886 Some(source.clone()),
5887 None::<String>,
5888 spec_context.clone(),
5889 None,
5890 ));
5891 }
5892 }
5893 if let Some(max) = maximum {
5894 let max_sem = semantics::time_to_semantic(max);
5895 if semantics::compare_semantic_times(def, &max_sem) == Ordering::Greater {
5896 errors.push(Error::validation_with_context(
5897 format!(
5898 "Type '{}' default time {} is after maximum {}",
5899 type_name, def, max
5900 ),
5901 Some(source.clone()),
5902 None::<String>,
5903 spec_context.clone(),
5904 None,
5905 ));
5906 }
5907 }
5908 }
5909 }
5910
5911 TypeSpecification::Boolean { .. } | TypeSpecification::Duration { .. } => {
5912 }
5914 TypeSpecification::Veto { .. } => {
5915 }
5918 TypeSpecification::Undetermined => unreachable!(
5919 "BUG: validate_type_specification_constraints called with Undetermined sentinel type; this type exists only during type inference"
5920 ),
5921 }
5922
5923 errors
5924}
5925
5926pub fn collect_bare_registry_refs(spec: &LemmaSpec) -> Vec<String> {
5932 if !spec.from_registry {
5933 return Vec::new();
5934 }
5935 let mut bare: Vec<String> = Vec::new();
5936 for data in &spec.data {
5937 match &data.value {
5938 ParsedDataValue::SpecReference(r) if !r.from_registry => {
5939 bare.push(r.name.clone());
5940 }
5941 ParsedDataValue::TypeDeclaration { from: Some(r), .. } if !r.from_registry => {
5942 bare.push(r.name.clone());
5943 }
5944 _ => {}
5945 }
5946 }
5947 bare
5948}
5949
5950#[cfg(test)]
5951mod validation_tests {
5952 use super::*;
5953 use crate::parsing::ast::{CommandArg, TypeConstraintCommand};
5954 use crate::planning::semantics::TypeSpecification;
5955 use rust_decimal::Decimal;
5956
5957 fn test_source() -> Source {
5958 Source::new(
5959 "<test>",
5960 crate::parsing::ast::Span {
5961 start: 0,
5962 end: 0,
5963 line: 1,
5964 col: 0,
5965 },
5966 )
5967 }
5968
5969 fn apply(
5970 specs: TypeSpecification,
5971 command: TypeConstraintCommand,
5972 args: &[CommandArg],
5973 ) -> TypeSpecification {
5974 let mut default = None;
5975 specs.apply_constraint(command, args, &mut default).unwrap()
5976 }
5977
5978 fn number_arg(n: i64) -> CommandArg {
5979 CommandArg::Literal(crate::literals::Value::Number(Decimal::from(n)))
5980 }
5981
5982 fn date_arg(s: &str) -> CommandArg {
5983 let dt = s.parse::<crate::literals::DateTimeValue>().expect("date");
5984 CommandArg::Literal(crate::literals::Value::Date(dt))
5985 }
5986
5987 fn time_arg(s: &str) -> CommandArg {
5988 let t = s.parse::<crate::literals::TimeValue>().expect("time");
5989 CommandArg::Literal(crate::literals::Value::Time(t))
5990 }
5991
5992 #[test]
5993 fn validate_number_minimum_greater_than_maximum() {
5994 let mut specs = TypeSpecification::number();
5995 specs = apply(specs, TypeConstraintCommand::Minimum, &[number_arg(100)]);
5996 specs = apply(specs, TypeConstraintCommand::Maximum, &[number_arg(50)]);
5997
5998 let src = test_source();
5999 let errors = validate_type_specifications(&specs, None, "test", &src, None);
6000 assert_eq!(errors.len(), 1);
6001 assert!(errors[0]
6002 .to_string()
6003 .contains("minimum 100 is greater than maximum 50"));
6004 }
6005
6006 #[test]
6007 fn validate_number_default_below_minimum() {
6008 let specs = TypeSpecification::Number {
6009 minimum: Some(Decimal::from(10)),
6010 maximum: None,
6011 decimals: None,
6012 precision: None,
6013 help: String::new(),
6014 };
6015 let default = ValueKind::Number(Decimal::from(5));
6016
6017 let src = test_source();
6018 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
6019 assert_eq!(errors.len(), 1);
6020 assert!(errors[0]
6021 .to_string()
6022 .contains("default value 5 is less than minimum 10"));
6023 }
6024
6025 #[test]
6026 fn validate_number_default_above_maximum() {
6027 let specs = TypeSpecification::Number {
6028 minimum: None,
6029 maximum: Some(Decimal::from(100)),
6030 decimals: None,
6031 precision: None,
6032 help: String::new(),
6033 };
6034 let default = ValueKind::Number(Decimal::from(150));
6035
6036 let src = test_source();
6037 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
6038 assert_eq!(errors.len(), 1);
6039 assert!(errors[0]
6040 .to_string()
6041 .contains("default value 150 is greater than maximum 100"));
6042 }
6043
6044 #[test]
6045 fn validate_number_default_valid() {
6046 let specs = TypeSpecification::Number {
6047 minimum: Some(Decimal::from(0)),
6048 maximum: Some(Decimal::from(100)),
6049 decimals: None,
6050 precision: None,
6051 help: String::new(),
6052 };
6053 let default = ValueKind::Number(Decimal::from(50));
6054
6055 let src = test_source();
6056 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
6057 assert!(errors.is_empty());
6058 }
6059
6060 #[test]
6061 fn text_minimum_command_is_rejected() {
6062 let specs = TypeSpecification::text();
6063 let res =
6064 specs.apply_constraint(TypeConstraintCommand::Minimum, &[number_arg(5)], &mut None);
6065 assert!(res.is_err());
6066 assert!(res
6067 .unwrap_err()
6068 .contains("Invalid command 'minimum' for text type"));
6069 }
6070
6071 #[test]
6072 fn text_maximum_command_is_rejected() {
6073 let specs = TypeSpecification::text();
6074 let res =
6075 specs.apply_constraint(TypeConstraintCommand::Maximum, &[number_arg(5)], &mut None);
6076 assert!(res.is_err());
6077 assert!(res
6078 .unwrap_err()
6079 .contains("Invalid command 'maximum' for text type"));
6080 }
6081
6082 #[test]
6083 fn validate_text_default_not_in_options() {
6084 let specs = TypeSpecification::Text {
6085 length: None,
6086 options: vec!["red".to_string(), "blue".to_string()],
6087 help: String::new(),
6088 };
6089 let default = ValueKind::Text("green".to_string());
6090
6091 let src = test_source();
6092 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
6093 assert_eq!(errors.len(), 1);
6094 assert!(errors[0]
6095 .to_string()
6096 .contains("default value 'green' is not in allowed options"));
6097 }
6098
6099 #[test]
6100 fn validate_ratio_minimum_greater_than_maximum() {
6101 let specs = TypeSpecification::Ratio {
6102 minimum: Some(Decimal::from(2)),
6103 maximum: Some(Decimal::from(1)),
6104 decimals: None,
6105 units: crate::planning::semantics::RatioUnits::new(),
6106 help: String::new(),
6107 };
6108
6109 let src = test_source();
6110 let errors = validate_type_specifications(&specs, None, "test", &src, None);
6111 assert_eq!(errors.len(), 1);
6112 assert!(errors[0]
6113 .to_string()
6114 .contains("minimum 2 is greater than maximum 1"));
6115 }
6116
6117 #[test]
6118 fn validate_date_minimum_after_maximum() {
6119 let mut specs = TypeSpecification::date();
6120 specs = apply(
6121 specs,
6122 TypeConstraintCommand::Minimum,
6123 &[date_arg("2024-12-31")],
6124 );
6125 specs = apply(
6126 specs,
6127 TypeConstraintCommand::Maximum,
6128 &[date_arg("2024-01-01")],
6129 );
6130
6131 let src = test_source();
6132 let errors = validate_type_specifications(&specs, None, "test", &src, None);
6133 assert_eq!(errors.len(), 1);
6134 assert!(
6135 errors[0].to_string().contains("minimum")
6136 && errors[0].to_string().contains("is after maximum")
6137 );
6138 }
6139
6140 #[test]
6141 fn validate_date_valid_range() {
6142 let mut specs = TypeSpecification::date();
6143 specs = apply(
6144 specs,
6145 TypeConstraintCommand::Minimum,
6146 &[date_arg("2024-01-01")],
6147 );
6148 specs = apply(
6149 specs,
6150 TypeConstraintCommand::Maximum,
6151 &[date_arg("2024-12-31")],
6152 );
6153
6154 let src = test_source();
6155 let errors = validate_type_specifications(&specs, None, "test", &src, None);
6156 assert!(errors.is_empty());
6157 }
6158
6159 #[test]
6160 fn validate_time_minimum_after_maximum() {
6161 let mut specs = TypeSpecification::time();
6162 specs = apply(
6163 specs,
6164 TypeConstraintCommand::Minimum,
6165 &[time_arg("23:00:00")],
6166 );
6167 specs = apply(
6168 specs,
6169 TypeConstraintCommand::Maximum,
6170 &[time_arg("10:00:00")],
6171 );
6172
6173 let src = test_source();
6174 let errors = validate_type_specifications(&specs, None, "test", &src, None);
6175 assert_eq!(errors.len(), 1);
6176 assert!(
6177 errors[0].to_string().contains("minimum")
6178 && errors[0].to_string().contains("is after maximum")
6179 );
6180 }
6181}