1use crate::engine::Context;
2use crate::parsing::ast::{
3 self as ast, CalendarUnit, CommandArg, Constraint, EffectiveDate, FillRhs, LemmaData,
4 LemmaRepository, LemmaRule, LemmaSpec, MetaValue, ParentType, PrimitiveKind,
5 TypeConstraintCommand, Value,
6};
7use crate::parsing::source::Source;
8use crate::planning::discovery;
9use crate::planning::semantics::{
10 self, calendar_decomposition, combine_decompositions, conversion_target_to_semantic,
11 duration_decomposition, number_with_unit_to_value_kind, parser_value_to_value_kind,
12 primitive_boolean, primitive_calendar, primitive_calendar_range, primitive_date,
13 primitive_date_range, primitive_number, primitive_number_range, primitive_text, primitive_time,
14 value_to_semantic, ArithmeticComputation, BaseQuantityVector, ComparisonComputation,
15 DataDefinition, DataPath, Expression, ExpressionKind, LemmaType, LiteralValue, PathSegment,
16 ReferenceTarget, RulePath, SemanticConversionTarget, TypeDefiningSpec, TypeExtends,
17 TypeSpecification, ValueKind,
18};
19use crate::Error;
20use ast::DataValue as ParsedDataValue;
21use indexmap::IndexMap;
22use std::cmp::Ordering;
23use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
24use std::fmt;
25use std::sync::Arc;
26
27type DataBindings = HashMap<Vec<String>, (BindingValue, Source)>;
36
37#[derive(Debug, Clone)]
46pub(crate) enum BindingValue {
47 Literal(ast::Value),
49 Reference {
51 target: ReferenceTarget,
52 constraints: Option<Vec<Constraint>>,
53 },
54}
55
56#[derive(Debug)]
57pub(crate) struct Graph {
58 main_spec: Arc<LemmaSpec>,
60 data: IndexMap<DataPath, DataDefinition>,
61 rules: BTreeMap<RulePath, RuleNode>,
62 execution_order: Vec<RulePath>,
63 reference_evaluation_order: Vec<DataPath>,
68}
69
70impl Graph {
71 pub(crate) fn data(&self) -> &IndexMap<DataPath, DataDefinition> {
72 &self.data
73 }
74
75 pub(crate) fn rules(&self) -> &BTreeMap<RulePath, RuleNode> {
76 &self.rules
77 }
78
79 pub(crate) fn rules_mut(&mut self) -> &mut BTreeMap<RulePath, RuleNode> {
80 &mut self.rules
81 }
82
83 pub(crate) fn execution_order(&self) -> &[RulePath] {
84 &self.execution_order
85 }
86
87 pub(crate) fn reference_evaluation_order(&self) -> &[DataPath] {
88 &self.reference_evaluation_order
89 }
90
91 pub(crate) fn main_spec(&self) -> &Arc<LemmaSpec> {
92 &self.main_spec
93 }
94
95 pub(crate) fn build_data(&self) -> IndexMap<DataPath, DataDefinition> {
98 struct PendingReference {
99 target: ReferenceTarget,
100 resolved_type: LemmaType,
101 local_constraints: Option<Vec<Constraint>>,
102 local_default: Option<ValueKind>,
103 }
104
105 let mut schema: HashMap<DataPath, LemmaType> = HashMap::new();
106 let mut declared_defaults: HashMap<DataPath, ValueKind> = HashMap::new();
107 let mut values: HashMap<DataPath, LiteralValue> = HashMap::new();
108 let mut spec_arcs: HashMap<DataPath, Arc<LemmaSpec>> = HashMap::new();
109 let mut references: HashMap<DataPath, PendingReference> = HashMap::new();
110
111 for (path, rfv) in self.data.iter() {
112 match rfv {
113 DataDefinition::Value { value, .. } => {
114 values.insert(path.clone(), value.clone());
115 schema.insert(path.clone(), value.lemma_type.clone());
116 }
117 DataDefinition::TypeDeclaration {
118 resolved_type,
119 declared_default,
120 ..
121 } => {
122 schema.insert(path.clone(), resolved_type.clone());
123 if let Some(dv) = declared_default {
124 declared_defaults.insert(path.clone(), dv.clone());
125 }
126 }
127 DataDefinition::Import { spec: spec_arc, .. } => {
128 spec_arcs.insert(path.clone(), Arc::clone(spec_arc));
129 }
130 DataDefinition::Reference {
131 target,
132 resolved_type,
133 local_constraints,
134 local_default,
135 ..
136 } => {
137 schema.insert(path.clone(), resolved_type.clone());
138 references.insert(
139 path.clone(),
140 PendingReference {
141 target: target.clone(),
142 resolved_type: resolved_type.clone(),
143 local_constraints: local_constraints.clone(),
144 local_default: local_default.clone(),
145 },
146 );
147 }
148 }
149 }
150
151 for (path, value) in values.iter_mut() {
152 let Some(schema_type) = schema.get(path).cloned() else {
153 continue;
154 };
155 match Self::coerce_literal_to_schema_type(value, &schema_type) {
156 Ok(coerced) => *value = coerced,
157 Err(msg) => unreachable!("Data {} incompatible: {}", path, msg),
158 }
159 }
160
161 let mut data = IndexMap::new();
162 for (path, rfv) in &self.data {
163 let source = rfv.source().clone();
164 if let Some(spec_arc) = spec_arcs.remove(path) {
165 data.insert(
166 path.clone(),
167 DataDefinition::Import {
168 spec: spec_arc,
169 source,
170 },
171 );
172 } else if let Some(pending) = references.remove(path) {
173 data.insert(
174 path.clone(),
175 DataDefinition::Reference {
176 target: pending.target,
177 resolved_type: pending.resolved_type,
178 local_constraints: pending.local_constraints,
179 local_default: pending.local_default,
180 source,
181 },
182 );
183 } else if let Some(value) = values.remove(path) {
184 data.insert(path.clone(), DataDefinition::Value { value, source });
185 } else {
186 let resolved_type = schema
187 .get(path)
188 .cloned()
189 .expect("non-spec-ref data has schema (value, reference, or type-only)");
190 let declared_default = declared_defaults.remove(path);
191 data.insert(
192 path.clone(),
193 DataDefinition::TypeDeclaration {
194 resolved_type,
195 declared_default,
196 source,
197 },
198 );
199 }
200 }
201 data
202 }
203
204 pub(crate) fn coerce_literal_to_schema_type(
205 lit: &LiteralValue,
206 schema_type: &LemmaType,
207 ) -> Result<LiteralValue, String> {
208 fn range_endpoint_schema_type(schema_type: &LemmaType) -> Option<LemmaType> {
209 match &schema_type.specifications {
210 TypeSpecification::NumberRange { .. } => {
211 Some(LemmaType::primitive(TypeSpecification::number()))
212 }
213 TypeSpecification::DateRange { .. } => {
214 Some(LemmaType::primitive(TypeSpecification::date()))
215 }
216 TypeSpecification::RatioRange { units, .. } => {
217 Some(LemmaType::primitive(TypeSpecification::Ratio {
218 minimum: None,
219 maximum: None,
220 decimals: None,
221 units: units.clone(),
222 help: String::new(),
223 }))
224 }
225 TypeSpecification::CalendarRange { .. } => {
226 Some(LemmaType::primitive(TypeSpecification::calendar()))
227 }
228 TypeSpecification::QuantityRange {
229 units,
230 decomposition,
231 canonical_unit,
232 ..
233 } => Some(LemmaType::primitive(TypeSpecification::Quantity {
234 minimum: None,
235 maximum: None,
236 decimals: None,
237 units: units.clone(),
238 traits: Vec::new(),
239 decomposition: decomposition.clone(),
240 canonical_unit: canonical_unit.clone(),
241 help: String::new(),
242 })),
243 _ => None,
244 }
245 }
246
247 if lit.lemma_type.specifications == schema_type.specifications {
248 let mut out = lit.clone();
249 out.lemma_type = schema_type.clone();
250 return Ok(out);
251 }
252 match (&schema_type.specifications, &lit.value) {
253 (TypeSpecification::Number { .. }, ValueKind::Number(_))
254 | (TypeSpecification::Text { .. }, ValueKind::Text(_))
255 | (TypeSpecification::Boolean { .. }, ValueKind::Boolean(_))
256 | (TypeSpecification::Date { .. }, ValueKind::Date(_))
257 | (TypeSpecification::Time { .. }, ValueKind::Time(_))
258 | (TypeSpecification::Calendar { .. }, ValueKind::Calendar(_, _)) => {
259 let mut out = lit.clone();
260 out.lemma_type = schema_type.clone();
261 Ok(out)
262 }
263 (TypeSpecification::Quantity { units, .. }, ValueKind::Quantity(_, unit_name, _)) => {
264 if !units.iter().any(|u| u.name.eq_ignore_ascii_case(unit_name)) {
265 return Err(format!(
266 "value {} cannot be used as type {}: unknown unit '{}'",
267 lit,
268 schema_type.name(),
269 unit_name
270 ));
271 }
272 let mut out = lit.clone();
273 out.lemma_type = schema_type.clone();
274 Ok(out)
275 }
276 (TypeSpecification::Ratio { units, .. }, ValueKind::Ratio(_, unit_name)) => {
277 if let Some(unit_name) = unit_name {
278 if !units.iter().any(|u| u.name.eq_ignore_ascii_case(unit_name)) {
279 return Err(format!(
280 "value {} cannot be used as type {}: unknown unit '{}'",
281 lit,
282 schema_type.name(),
283 unit_name
284 ));
285 }
286 }
287 let mut out = lit.clone();
288 out.lemma_type = schema_type.clone();
289 Ok(out)
290 }
291 (
292 TypeSpecification::NumberRange { .. }
293 | TypeSpecification::DateRange { .. }
294 | TypeSpecification::RatioRange { .. }
295 | TypeSpecification::CalendarRange { .. }
296 | TypeSpecification::QuantityRange { .. },
297 ValueKind::Range(left, right),
298 ) => {
299 let endpoint_schema_type =
300 range_endpoint_schema_type(schema_type).unwrap_or_else(|| {
301 unreachable!("BUG: range_endpoint_schema_type missing range schema arm")
302 });
303 let coerced_left =
304 Self::coerce_literal_to_schema_type(left.as_ref(), &endpoint_schema_type)?;
305 let coerced_right =
306 Self::coerce_literal_to_schema_type(right.as_ref(), &endpoint_schema_type)?;
307 Ok(LiteralValue {
308 value: ValueKind::Range(Box::new(coerced_left), Box::new(coerced_right)),
309 lemma_type: schema_type.clone(),
310 })
311 }
312 (TypeSpecification::Ratio { .. }, ValueKind::Number(n)) => {
313 Ok(LiteralValue::ratio_with_type(*n, None, schema_type.clone()))
314 }
315 _ => Err(format!(
316 "value {} cannot be used as type {}",
317 lit,
318 schema_type.name()
319 )),
320 }
321 }
322
323 fn resolve_data_reference_types(&mut self) -> Result<(), Vec<Error>> {
335 let mut errors: Vec<Error> = Vec::new();
336 let mut updates: Vec<(DataPath, LemmaType, Option<ValueKind>)> = Vec::new();
337
338 for (reference_path, entry) in &self.data {
339 let DataDefinition::Reference {
340 target,
341 resolved_type: provisional,
342 local_constraints,
343 source,
344 ..
345 } = entry
346 else {
347 continue;
348 };
349
350 let target_data_path = match target {
351 ReferenceTarget::Data(path) => path,
352 ReferenceTarget::Rule(_) => continue,
353 };
354
355 let Some(target_entry) = self.data.get(target_data_path) else {
356 errors.push(reference_error(
357 &self.main_spec,
358 source,
359 format!(
360 "Data reference '{}' target '{}' does not exist",
361 reference_path, target_data_path
362 ),
363 ));
364 continue;
365 };
366
367 let Some(target_type) = target_entry.schema_type().cloned() else {
368 errors.push(reference_error(
369 &self.main_spec,
370 source,
371 format!(
372 "Data reference '{}' target '{}' is a spec reference and cannot carry a value",
373 reference_path, target_data_path
374 ),
375 ));
376 continue;
377 };
378
379 let lhs_declared_type: Option<&LemmaType> = if provisional.is_undetermined() {
380 None
381 } else {
382 Some(provisional)
383 };
384
385 if let Some(lhs) = lhs_declared_type {
386 if let Some(msg) = reference_kind_mismatch_message(
387 lhs,
388 &target_type,
389 reference_path,
390 target_data_path,
391 "target",
392 ) {
393 errors.push(reference_error(&self.main_spec, source, msg));
394 continue;
395 }
396 }
397
398 let mut merged = match lhs_declared_type {
403 Some(lhs) => lhs.clone(),
404 None => target_type.clone(),
405 };
406 let mut captured_default: Option<ValueKind> = None;
407 if let Some(constraints) = local_constraints {
408 let constraint_type_name = merged.name();
409 match apply_constraints_to_spec(
410 &self.main_spec,
411 &constraint_type_name,
412 merged.specifications.clone(),
413 constraints,
414 source,
415 &mut captured_default,
416 ) {
417 Ok(specs) => merged.specifications = specs,
418 Err(errs) => {
419 errors.extend(errs);
420 continue;
421 }
422 }
423 }
424
425 updates.push((reference_path.clone(), merged, captured_default));
426 }
427
428 for (path, new_type, new_default) in updates {
429 if let Some(DataDefinition::Reference {
430 resolved_type,
431 local_default,
432 ..
433 }) = self.data.get_mut(&path)
434 {
435 *resolved_type = new_type;
436 if new_default.is_some() {
437 *local_default = new_default;
438 }
439 } else {
440 unreachable!("BUG: reference path disappeared between collect and update phases");
441 }
442 }
443
444 if errors.is_empty() {
445 Ok(())
446 } else {
447 Err(errors)
448 }
449 }
450
451 fn resolve_rule_reference_types(
460 &mut self,
461 computed_rule_types: &HashMap<RulePath, LemmaType>,
462 ) -> Result<(), Vec<Error>> {
463 let mut errors: Vec<Error> = Vec::new();
464 let mut updates: Vec<(DataPath, LemmaType, Option<ValueKind>)> = Vec::new();
465
466 for (reference_path, entry) in &self.data {
467 let DataDefinition::Reference {
468 target,
469 resolved_type: provisional,
470 local_constraints,
471 source,
472 ..
473 } = entry
474 else {
475 continue;
476 };
477
478 let target_rule_path = match target {
479 ReferenceTarget::Rule(path) => path,
480 ReferenceTarget::Data(_) => continue,
481 };
482
483 let Some(target_type) = computed_rule_types.get(target_rule_path) else {
484 errors.push(reference_error(
485 &self.main_spec,
486 source,
487 format!(
488 "Data reference '{}' target rule '{}' does not exist",
489 reference_path, target_rule_path
490 ),
491 ));
492 continue;
493 };
494
495 if target_type.vetoed() || target_type.is_undetermined() {
500 let mut merged = target_type.clone();
501 let mut captured_default: Option<ValueKind> = None;
502 if let Some(constraints) = local_constraints {
503 let constraint_type_name = merged.name();
504 match apply_constraints_to_spec(
505 &self.main_spec,
506 &constraint_type_name,
507 merged.specifications.clone(),
508 constraints,
509 source,
510 &mut captured_default,
511 ) {
512 Ok(specs) => merged.specifications = specs,
513 Err(errs) => {
514 errors.extend(errs);
515 continue;
516 }
517 }
518 }
519 updates.push((reference_path.clone(), merged, captured_default));
520 continue;
521 }
522
523 let lhs_declared_type: Option<&LemmaType> = if provisional.is_undetermined() {
524 None
525 } else {
526 Some(provisional)
527 };
528
529 if let Some(lhs) = lhs_declared_type {
530 if let Some(msg) = reference_kind_mismatch_message(
531 lhs,
532 target_type,
533 reference_path,
534 target_rule_path,
535 "target rule",
536 ) {
537 errors.push(reference_error(&self.main_spec, source, msg));
538 continue;
539 }
540 }
541
542 let mut merged = match lhs_declared_type {
545 Some(lhs) => lhs.clone(),
546 None => target_type.clone(),
547 };
548 let mut captured_default: Option<ValueKind> = None;
549 if let Some(constraints) = local_constraints {
550 let constraint_type_name = merged.name();
551 match apply_constraints_to_spec(
552 &self.main_spec,
553 &constraint_type_name,
554 merged.specifications.clone(),
555 constraints,
556 source,
557 &mut captured_default,
558 ) {
559 Ok(specs) => merged.specifications = specs,
560 Err(errs) => {
561 errors.extend(errs);
562 continue;
563 }
564 }
565 }
566
567 updates.push((reference_path.clone(), merged, captured_default));
568 }
569
570 for (path, new_type, new_default) in updates {
571 if let Some(DataDefinition::Reference {
572 resolved_type,
573 local_default,
574 ..
575 }) = self.data.get_mut(&path)
576 {
577 *resolved_type = new_type;
578 if new_default.is_some() {
579 *local_default = new_default;
580 }
581 } else {
582 unreachable!(
583 "BUG: rule-target reference path disappeared between collect and update phases"
584 );
585 }
586 }
587
588 if errors.is_empty() {
589 Ok(())
590 } else {
591 Err(errors)
592 }
593 }
594
595 fn add_rule_reference_dependency_edges(&mut self) {
605 let reference_to_rule: HashMap<DataPath, RulePath> =
606 self.transitive_reference_to_rule_map();
607
608 if reference_to_rule.is_empty() {
609 return;
610 }
611
612 let mut updates: Vec<(RulePath, RulePath)> = Vec::new();
613 for (rule_path, rule_node) in &self.rules {
614 let mut found: BTreeSet<RulePath> = BTreeSet::new();
615 for (cond, result) in &rule_node.branches {
616 if let Some(c) = cond {
617 collect_rule_reference_dependencies(c, &reference_to_rule, &mut found);
618 }
619 collect_rule_reference_dependencies(result, &reference_to_rule, &mut found);
620 }
621 for target in found {
622 updates.push((rule_path.clone(), target));
623 }
624 }
625
626 for (rule_path, target) in updates {
627 if let Some(node) = self.rules.get_mut(&rule_path) {
628 node.depends_on_rules.insert(target);
629 }
630 }
631 }
632
633 fn transitive_reference_to_rule_map(&self) -> HashMap<DataPath, RulePath> {
640 let mut out: HashMap<DataPath, RulePath> = HashMap::new();
641 for (path, def) in &self.data {
642 if !matches!(def, DataDefinition::Reference { .. }) {
643 continue;
644 }
645 let mut visited: HashSet<DataPath> = HashSet::new();
646 let mut cursor: DataPath = path.clone();
647 loop {
648 if !visited.insert(cursor.clone()) {
649 break;
650 }
651 let Some(DataDefinition::Reference { target, .. }) = self.data.get(&cursor) else {
652 break;
653 };
654 match target {
655 ReferenceTarget::Data(next) => cursor = next.clone(),
656 ReferenceTarget::Rule(rule_path) => {
657 out.insert(path.clone(), rule_path.clone());
658 break;
659 }
660 }
661 }
662 }
663 out
664 }
665
666 fn compute_reference_evaluation_order(&self) -> Result<Vec<DataPath>, Vec<Error>> {
673 let reference_paths: Vec<DataPath> = self
674 .data
675 .iter()
676 .filter_map(|(p, d)| match d {
677 DataDefinition::Reference {
678 target: ReferenceTarget::Data(_),
679 ..
680 } => Some(p.clone()),
681 _ => None,
682 })
683 .collect();
684
685 if reference_paths.is_empty() {
686 return Ok(Vec::new());
687 }
688
689 let reference_set: BTreeSet<DataPath> = reference_paths.iter().cloned().collect();
690 let mut in_degree: BTreeMap<DataPath, usize> = BTreeMap::new();
691 let mut dependents: BTreeMap<DataPath, Vec<DataPath>> = BTreeMap::new();
692 for p in &reference_paths {
693 in_degree.insert(p.clone(), 0);
694 dependents.insert(p.clone(), Vec::new());
695 }
696
697 for p in &reference_paths {
698 let Some(DataDefinition::Reference { target, .. }) = self.data.get(p) else {
699 unreachable!("BUG: reference entry lost between collect and walk");
700 };
701 if let ReferenceTarget::Data(target_path) = target {
702 if reference_set.contains(target_path) {
703 *in_degree
704 .get_mut(p)
705 .expect("BUG: reference missing in_degree") += 1;
706 dependents
707 .get_mut(target_path)
708 .expect("BUG: reference missing dependents list")
709 .push(p.clone());
710 }
711 }
712 }
713
714 let mut queue: VecDeque<DataPath> = in_degree
715 .iter()
716 .filter(|(_, d)| **d == 0)
717 .map(|(p, _)| p.clone())
718 .collect();
719
720 let mut result: Vec<DataPath> = Vec::new();
721 while let Some(path) = queue.pop_front() {
722 result.push(path.clone());
723 if let Some(deps) = dependents.get(&path) {
724 for dependent in deps.clone() {
725 let degree = in_degree
726 .get_mut(&dependent)
727 .expect("BUG: reference dependent missing in_degree");
728 *degree -= 1;
729 if *degree == 0 {
730 queue.push_back(dependent);
731 }
732 }
733 }
734 }
735
736 if result.len() != reference_paths.len() {
737 let cycle_members: Vec<DataPath> = reference_paths
738 .iter()
739 .filter(|p| !result.contains(p))
740 .cloned()
741 .collect();
742 let cycle_display: String = cycle_members
743 .iter()
744 .map(|p| p.to_string())
745 .collect::<Vec<_>>()
746 .join(", ");
747 let errors: Vec<Error> = cycle_members
748 .iter()
749 .filter_map(|p| {
750 self.data.get(p).map(|entry| {
751 reference_error(
752 &self.main_spec,
753 entry.source(),
754 format!("Circular data reference ({})", cycle_display),
755 )
756 })
757 })
758 .collect();
759 return Err(errors);
760 }
761
762 Ok(result)
763 }
764
765 fn topological_sort(&self) -> Result<Vec<RulePath>, Vec<Error>> {
766 let mut in_degree: BTreeMap<RulePath, usize> = BTreeMap::new();
767 let mut dependents: BTreeMap<RulePath, Vec<RulePath>> = BTreeMap::new();
768 let mut queue = VecDeque::new();
769 let mut result = Vec::new();
770
771 for rule_path in self.rules.keys() {
772 in_degree.insert(rule_path.clone(), 0);
773 dependents.insert(rule_path.clone(), Vec::new());
774 }
775
776 for (rule_path, rule_node) in &self.rules {
777 for dependency in &rule_node.depends_on_rules {
778 if self.rules.contains_key(dependency) {
779 if let Some(degree) = in_degree.get_mut(rule_path) {
780 *degree += 1;
781 }
782 if let Some(deps) = dependents.get_mut(dependency) {
783 deps.push(rule_path.clone());
784 }
785 }
786 }
787 }
788
789 for (rule_path, degree) in &in_degree {
790 if *degree == 0 {
791 queue.push_back(rule_path.clone());
792 }
793 }
794
795 while let Some(rule_path) = queue.pop_front() {
796 result.push(rule_path.clone());
797
798 if let Some(dependent_rules) = dependents.get(&rule_path) {
799 for dependent in dependent_rules {
800 if let Some(degree) = in_degree.get_mut(dependent) {
801 *degree -= 1;
802 if *degree == 0 {
803 queue.push_back(dependent.clone());
804 }
805 }
806 }
807 }
808 }
809
810 if result.len() != self.rules.len() {
811 let missing: Vec<RulePath> = self
812 .rules
813 .keys()
814 .filter(|rule| !result.contains(rule))
815 .cloned()
816 .collect();
817 let cycle: Vec<Source> = missing
818 .iter()
819 .filter_map(|rule| self.rules.get(rule).map(|n| n.source.clone()))
820 .collect();
821
822 if cycle.is_empty() {
823 unreachable!(
824 "BUG: circular dependency detected but no sources could be collected ({} missing rules)",
825 missing.len()
826 );
827 }
828 let rules_involved: String = missing
829 .iter()
830 .map(|rp| rp.rule.as_str())
831 .collect::<Vec<_>>()
832 .join(", ");
833 let message = format!("Circular dependency (rules: {})", rules_involved);
834 let errors: Vec<Error> = cycle
835 .into_iter()
836 .map(|source| {
837 Error::validation_with_context(
838 message.clone(),
839 Some(source),
840 None::<String>,
841 Some(Arc::clone(&self.main_spec)),
842 None,
843 )
844 })
845 .collect();
846 return Err(errors);
847 }
848
849 Ok(result)
850 }
851}
852
853#[derive(Debug)]
854pub(crate) struct RuleNode {
855 pub branches: Vec<(Option<Expression>, Expression)>,
858 pub source: Source,
859
860 pub depends_on_rules: BTreeSet<RulePath>,
861
862 pub rule_type: LemmaType,
865
866 pub spec_arc: Arc<LemmaSpec>,
868}
869
870type ResolvedTypesMap = Vec<(Arc<LemmaRepository>, Arc<LemmaSpec>, ResolvedSpecTypes)>;
871
872struct GraphBuilder<'a> {
873 data: IndexMap<DataPath, DataDefinition>,
874 rules: BTreeMap<RulePath, RuleNode>,
875 context: &'a Context,
876 local_types: ResolvedTypesMap,
877 errors: Vec<Error>,
878 main_spec: Arc<LemmaSpec>,
879 main_repository: Arc<ast::LemmaRepository>,
880}
881
882fn reference_error(main_spec: &Arc<LemmaSpec>, source: &Source, message: String) -> Error {
883 Error::validation_with_context(
884 message,
885 Some(source.clone()),
886 None::<String>,
887 Some(Arc::clone(main_spec)),
888 None,
889 )
890}
891
892fn reference_kind_mismatch_message<P: fmt::Display>(
909 lhs: &LemmaType,
910 target_type: &LemmaType,
911 reference_path: &DataPath,
912 target_path: &P,
913 target_kind_label: &str,
914) -> Option<String> {
915 if !lhs.has_same_base_type(target_type) {
916 return Some(format!(
917 "Data reference '{}' type mismatch: declared as '{}' but {} '{}' is '{}'",
918 reference_path,
919 lhs.name(),
920 target_kind_label,
921 target_path,
922 target_type.name(),
923 ));
924 }
925 if lhs.is_quantity() && !lhs.same_quantity_family(target_type) {
926 let lhs_family = lhs.quantity_family_name().expect(
927 "BUG: declared quantity data must carry a family name; \
928 anonymous quantity types only arise from runtime synthesis \
929 and never appear as a reference's LHS-declared type",
930 );
931 let target_family = target_type.quantity_family_name().expect(
932 "BUG: declared quantity data must carry a family name; \
933 anonymous quantity types only arise from runtime synthesis \
934 and never appear as a reference target's schema type",
935 );
936 return Some(format!(
937 "Data reference '{}' quantity family mismatch: declared as '{}' (family '{}') but {} '{}' is '{}' (family '{}')",
938 reference_path,
939 lhs.name(),
940 lhs_family,
941 target_kind_label,
942 target_path,
943 target_type.name(),
944 target_family,
945 ));
946 }
947 None
948}
949
950fn constraint_application_type_name(parent: &ParentType, data_name: &str) -> String {
952 match parent {
953 ParentType::Custom { name } => name.clone(),
954 ParentType::Qualified { inner, .. } => constraint_application_type_name(inner, data_name),
955 ParentType::Primitive { .. } => data_name.to_string(),
956 }
957}
958
959fn apply_constraints_to_spec(
964 spec: &Arc<LemmaSpec>,
965 type_name: &str,
966 mut specs: TypeSpecification,
967 constraints: &[Constraint],
968 source: &crate::parsing::source::Source,
969 declared_default: &mut Option<ValueKind>,
970) -> Result<TypeSpecification, Vec<Error>> {
971 let mut errors = Vec::new();
972 let mut apply_one = |specs: TypeSpecification,
973 command: TypeConstraintCommand,
974 args: &[CommandArg],
975 declared_default: &mut Option<ValueKind>|
976 -> TypeSpecification {
977 let specs_clone = specs.clone();
978 let mut default_before = declared_default.clone();
979 match specs.apply_constraint(type_name, command, args, &mut default_before) {
980 Ok(updated_specs) => {
981 *declared_default = default_before;
982 updated_specs
983 }
984 Err(e) => {
985 errors.push(Error::validation_with_context(
986 format!("Failed to apply constraint '{}': {}", command, e),
987 Some(source.clone()),
988 None::<String>,
989 Some(Arc::clone(spec)),
990 None,
991 ));
992 specs_clone
993 }
994 }
995 };
996
997 let mut deferred: Vec<(TypeConstraintCommand, Vec<CommandArg>)> = Vec::new();
998 for (command, args) in constraints {
999 if matches!(
1000 command,
1001 TypeConstraintCommand::Unit | TypeConstraintCommand::Trait
1002 ) {
1003 specs = apply_one(specs, *command, args, declared_default);
1004 } else {
1005 deferred.push((*command, args.clone()));
1006 }
1007 }
1008 for (command, args) in deferred {
1009 specs = apply_one(specs, command, &args, declared_default);
1010 }
1011 if !errors.is_empty() {
1012 return Err(errors);
1013 }
1014 Ok(specs)
1015}
1016
1017impl Graph {
1018 pub(crate) fn build(
1020 context: &Context,
1021 repository: &Arc<LemmaRepository>,
1022 main_spec: &Arc<LemmaSpec>,
1023 dag: &[(Arc<LemmaRepository>, Arc<LemmaSpec>)],
1024 effective: &EffectiveDate,
1025 ) -> Result<(Graph, ResolvedTypesMap), Vec<Error>> {
1026 let mut type_resolver = TypeResolver::new(context);
1027
1028 let mut type_errors: Vec<Error> = Vec::new();
1029 for (repo, spec) in dag {
1030 type_errors.extend(type_resolver.register_all(repo, spec));
1031 }
1032
1033 let (data, rules, graph_errors, local_types) = {
1034 let mut builder = GraphBuilder {
1035 data: IndexMap::new(),
1036 rules: BTreeMap::new(),
1037 context,
1038 local_types: Vec::new(),
1039 errors: Vec::new(),
1040 main_spec: Arc::clone(main_spec),
1041 main_repository: Arc::clone(repository),
1042 };
1043
1044 builder.build_spec(
1045 main_spec,
1046 repository,
1047 Vec::new(),
1048 HashMap::new(),
1049 effective,
1050 &mut type_resolver,
1051 )?;
1052
1053 (
1054 builder.data,
1055 builder.rules,
1056 builder.errors,
1057 builder.local_types,
1058 )
1059 };
1060
1061 let mut graph = Graph {
1062 data,
1063 rules,
1064 execution_order: Vec::new(),
1065 reference_evaluation_order: Vec::new(),
1066 main_spec: Arc::clone(main_spec),
1067 };
1068
1069 let validation_errors = match graph.validate(&local_types) {
1070 Ok(()) => Vec::new(),
1071 Err(errors) => errors,
1072 };
1073
1074 let mut all_errors = type_errors;
1075 all_errors.extend(graph_errors);
1076 all_errors.extend(validation_errors);
1077
1078 if all_errors.is_empty() {
1079 Ok((graph, local_types))
1080 } else {
1081 Err(all_errors)
1082 }
1083 }
1084
1085 fn validate(&mut self, resolved_types: &ResolvedTypesMap) -> Result<(), Vec<Error>> {
1086 let mut errors = Vec::new();
1087
1088 if let Err(structural_errors) = check_all_rule_references_exist(self) {
1090 errors.extend(structural_errors);
1091 }
1092 if let Err(collision_errors) = check_data_and_rule_name_collisions(self) {
1093 errors.extend(collision_errors);
1094 }
1095
1096 if let Err(reference_errors) = self.resolve_data_reference_types() {
1100 errors.extend(reference_errors);
1101 }
1102
1103 let reference_order = match self.compute_reference_evaluation_order() {
1107 Ok(order) => order,
1108 Err(circular_errors) => {
1109 errors.extend(circular_errors);
1110 return Err(errors);
1111 }
1112 };
1113
1114 self.add_rule_reference_dependency_edges();
1120
1121 let execution_order = match self.topological_sort() {
1122 Ok(order) => order,
1123 Err(circular_errors) => {
1124 errors.extend(circular_errors);
1125 return Err(errors);
1126 }
1127 };
1128
1129 let inferred_types = infer_rule_types(self, &execution_order, resolved_types);
1137
1138 if let Err(rule_reference_errors) = self.resolve_rule_reference_types(&inferred_types) {
1143 errors.extend(rule_reference_errors);
1144 }
1145
1146 if let Err(type_errors) =
1148 check_rule_types(self, &execution_order, &inferred_types, resolved_types)
1149 {
1150 errors.extend(type_errors);
1151 }
1152
1153 if !errors.is_empty() {
1154 return Err(errors);
1155 }
1156
1157 apply_inferred_types(self, inferred_types);
1159 self.execution_order = execution_order;
1160 self.reference_evaluation_order = reference_order;
1161 Ok(())
1162 }
1163}
1164
1165fn uses_import_surface_syntax(alias: &str, target_spec: &str) -> String {
1166 if alias == target_spec {
1167 format!("uses {alias}")
1168 } else {
1169 format!("uses {alias}: {target_spec}")
1170 }
1171}
1172
1173fn is_uses_vs_data_clash(existing: &DataDefinition, incoming: &ParsedDataValue) -> bool {
1174 matches!(
1175 (existing, incoming),
1176 (
1177 DataDefinition::Import { .. },
1178 ParsedDataValue::Definition { .. }
1179 )
1180 ) || matches!(
1181 (existing, incoming),
1182 (
1183 DataDefinition::TypeDeclaration { .. } | DataDefinition::Value { .. },
1184 ParsedDataValue::Import(_)
1185 )
1186 )
1187}
1188
1189fn qualified_type_name_from_definition(incoming: &ParsedDataValue) -> Option<&str> {
1190 let ParsedDataValue::Definition {
1191 base: Some(ParentType::Qualified { inner, .. }),
1192 ..
1193 } = incoming
1194 else {
1195 return None;
1196 };
1197 match inner.as_ref() {
1198 ParentType::Custom { name } => Some(name.as_str()),
1199 _ => None,
1200 }
1201}
1202
1203fn uses_vs_data_duplicate_message(
1204 name: &str,
1205 existing: &DataDefinition,
1206 incoming: &ParsedDataValue,
1207) -> (String, Option<String>) {
1208 let (alias, target_spec) = match (existing, incoming) {
1209 (DataDefinition::Import { spec, .. }, ParsedDataValue::Definition { .. }) => {
1210 (name, spec.name.as_str())
1211 }
1212 (
1213 DataDefinition::TypeDeclaration { .. } | DataDefinition::Value { .. },
1214 ParsedDataValue::Import(spec_ref),
1215 ) => (name, spec_ref.name.as_str()),
1216 _ => unreachable!("uses_vs_data_duplicate_message requires a uses vs data clash"),
1217 };
1218 let uses_syntax = uses_import_surface_syntax(alias, target_spec);
1219 let import_alias = format!("{alias}_spec");
1220 let message = format!(
1221 "You used the name `{alias}` in both `{uses_syntax}` and `data {alias}`. A `uses` import and a `data` definition can't share the same name.",
1222 );
1223 let suggestion = match qualified_type_name_from_definition(incoming) {
1224 Some(type_name) => format!(
1225 "Try `uses {import_alias}: {target_spec}` and `data {alias}: {import_alias}.{type_name}`."
1226 ),
1227 _ => format!("Try `uses {import_alias}: {target_spec}` with a different name than `{alias}`."),
1228 };
1229 (message, Some(suggestion))
1230}
1231
1232impl<'a> GraphBuilder<'a> {
1233 fn engine_error(&self, message: impl Into<String>, source: &Source) -> Error {
1234 Error::validation_with_context(
1235 message.into(),
1236 Some(source.clone()),
1237 None::<String>,
1238 Some(Arc::clone(&self.main_spec)),
1239 None,
1240 )
1241 }
1242
1243 fn process_meta_fields(&mut self, spec: &LemmaSpec) {
1244 let mut seen = HashSet::new();
1245 for field in &spec.meta_fields {
1246 if field.key == "title" && !matches!(field.value, MetaValue::Literal(Value::Text(_))) {
1248 self.errors.push(self.engine_error(
1249 "Meta 'title' must be a text literal",
1250 &field.source_location,
1251 ));
1252 }
1253
1254 if !seen.insert(field.key.clone()) {
1255 self.errors.push(self.engine_error(
1256 format!("Duplicate meta key '{}'", field.key),
1257 &field.source_location,
1258 ));
1259 }
1260 }
1261 }
1262
1263 fn resolve_spec_ref(
1264 &self,
1265 spec_ref: &ast::SpecRef,
1266 effective: &EffectiveDate,
1267 consumer_spec: &Arc<LemmaSpec>,
1268 consumer_repository: &Arc<LemmaRepository>,
1269 ) -> Result<(Arc<LemmaRepository>, Arc<LemmaSpec>), Error> {
1270 discovery::resolve_spec_ref(
1271 self.context,
1272 spec_ref,
1273 consumer_repository,
1274 consumer_spec,
1275 effective,
1276 None,
1277 )
1278 }
1279
1280 fn resolve_data_binding(
1287 &mut self,
1288 data: &LemmaData,
1289 current_segment_names: &[String],
1290 parent_spec: &Arc<LemmaSpec>,
1291 effective: &EffectiveDate,
1292 ) -> Option<(Vec<String>, BindingValue, Source)> {
1293 let binding_path_display = format!("{}", data.reference);
1294
1295 let mut walk_spec = Arc::clone(parent_spec);
1296
1297 for segment in &data.reference.segments {
1298 let Some(seg_data) = walk_spec
1299 .data
1300 .iter()
1301 .find(|f| f.reference.segments.is_empty() && f.reference.name == *segment)
1302 else {
1303 self.errors.push(self.engine_error(
1304 format!(
1305 "Data binding path '{}': data '{}' not found in spec '{}'",
1306 binding_path_display, segment, walk_spec.name
1307 ),
1308 &data.source_location,
1309 ));
1310 return None;
1311 };
1312
1313 let spec_ref = match &seg_data.value {
1314 ParsedDataValue::Import(sr) => sr,
1315 _ => {
1316 self.errors.push(self.engine_error(
1317 format!(
1318 "Data binding path '{}': '{}' in spec '{}' is not a spec reference",
1319 binding_path_display, segment, walk_spec.name
1320 ),
1321 &data.source_location,
1322 ));
1323 return None;
1324 }
1325 };
1326
1327 let walk_repository = discovery::lookup_owning_repository(self.context, &walk_spec)
1328 .unwrap_or_else(|| Arc::clone(&self.main_repository));
1329 walk_spec =
1330 match self.resolve_spec_ref(spec_ref, effective, &walk_spec, &walk_repository) {
1331 Ok((_, arc)) => arc,
1332 Err(e) => {
1333 self.errors.push(e);
1334 return None;
1335 }
1336 };
1337 }
1338
1339 if !walk_spec
1340 .data
1341 .iter()
1342 .any(|d| d.reference.segments.is_empty() && d.reference.name == data.reference.name)
1343 {
1344 self.errors.push(self.engine_error(
1345 format!(
1346 "Data binding path '{}': data '{}' not found in spec '{}'",
1347 binding_path_display, data.reference.name, walk_spec.name
1348 ),
1349 &data.source_location,
1350 ));
1351 return None;
1352 }
1353
1354 let mut binding_key: Vec<String> = current_segment_names.to_vec();
1356 binding_key.extend(data.reference.segments.iter().cloned());
1357 binding_key.push(data.reference.name.clone());
1358
1359 let binding_value = match &data.value {
1360 ParsedDataValue::Fill(FillRhs::Literal(v)) => BindingValue::Literal(v.clone()),
1361 ParsedDataValue::Fill(FillRhs::Reference { target }) => {
1362 let resolved_target = self.resolve_reference_target_in_spec(
1363 target,
1364 &data.source_location,
1365 parent_spec,
1366 current_segment_names,
1367 effective,
1368 )?;
1369 BindingValue::Reference {
1370 target: resolved_target,
1371 constraints: None,
1372 }
1373 }
1374 ParsedDataValue::Definition { value: Some(v), .. }
1375 if data.value.is_definition_literal_only() =>
1376 {
1377 BindingValue::Literal(v.clone())
1378 }
1379 ParsedDataValue::Import(_) => {
1380 unreachable!(
1381 "BUG: build_data_bindings must reject Import bindings before calling resolve_data_binding"
1382 );
1383 }
1384 ParsedDataValue::Definition { .. } => {
1385 unreachable!(
1386 "BUG: build_data_bindings must reject non-literal Definition bindings before calling resolve_data_binding"
1387 );
1388 }
1389 };
1390
1391 Some((binding_key, binding_value, data.source_location.clone()))
1392 }
1393
1394 fn resolve_reference_target_in_spec(
1401 &mut self,
1402 reference: &ast::Reference,
1403 reference_source: &Source,
1404 containing_spec_arc: &Arc<LemmaSpec>,
1405 containing_segments_names: &[String],
1406 effective: &EffectiveDate,
1407 ) -> Option<ReferenceTarget> {
1408 let containing_data_map: HashMap<String, LemmaData> = containing_spec_arc
1409 .data
1410 .iter()
1411 .filter(|d| d.reference.is_local())
1412 .map(|d| (d.reference.name.clone(), d.clone()))
1413 .collect();
1414
1415 let containing_rule_names: HashSet<&str> = containing_spec_arc
1416 .rules
1417 .iter()
1418 .map(|r| r.name.as_str())
1419 .collect();
1420
1421 let containing_segments: Vec<PathSegment> = containing_segments_names
1422 .iter()
1423 .map(|name| PathSegment {
1424 data: name.clone(),
1425 spec: containing_spec_arc.name.clone(),
1426 })
1427 .collect();
1428
1429 if reference.segments.is_empty() {
1430 let is_data = containing_data_map.contains_key(&reference.name);
1431 let is_rule = containing_rule_names.contains(reference.name.as_str());
1432 if is_data && is_rule {
1433 self.errors.push(self.engine_error(
1434 format!(
1435 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
1436 reference.name, containing_spec_arc.name
1437 ),
1438 reference_source,
1439 ));
1440 return None;
1441 }
1442 if is_data {
1443 return Some(ReferenceTarget::Data(DataPath {
1444 segments: containing_segments,
1445 data: reference.name.clone(),
1446 }));
1447 }
1448 if is_rule {
1449 return Some(ReferenceTarget::Rule(RulePath {
1450 segments: containing_segments,
1451 rule: reference.name.clone(),
1452 }));
1453 }
1454 self.errors.push(self.engine_error(
1455 format!(
1456 "Reference target '{}' not found in spec '{}'",
1457 reference.name, containing_spec_arc.name
1458 ),
1459 reference_source,
1460 ));
1461 return None;
1462 }
1463
1464 let (resolved_segments, target_spec_arc) = self.resolve_path_segments(
1465 &reference.segments,
1466 reference_source,
1467 containing_data_map,
1468 containing_segments,
1469 Arc::clone(containing_spec_arc),
1470 effective,
1471 )?;
1472
1473 let target_data_names: HashSet<&str> = target_spec_arc
1474 .data
1475 .iter()
1476 .filter(|d| d.reference.is_local())
1477 .map(|d| d.reference.name.as_str())
1478 .collect();
1479 let target_rule_names: HashSet<&str> = target_spec_arc
1480 .rules
1481 .iter()
1482 .map(|r| r.name.as_str())
1483 .collect();
1484 let is_data = target_data_names.contains(reference.name.as_str());
1485 let is_rule = target_rule_names.contains(reference.name.as_str());
1486
1487 if is_data && is_rule {
1488 self.errors.push(self.engine_error(
1489 format!(
1490 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
1491 reference.name, target_spec_arc.name
1492 ),
1493 reference_source,
1494 ));
1495 return None;
1496 }
1497 if is_data {
1498 return Some(ReferenceTarget::Data(DataPath {
1499 segments: resolved_segments,
1500 data: reference.name.clone(),
1501 }));
1502 }
1503 if is_rule {
1504 return Some(ReferenceTarget::Rule(RulePath {
1505 segments: resolved_segments,
1506 rule: reference.name.clone(),
1507 }));
1508 }
1509
1510 self.errors.push(self.engine_error(
1511 format!(
1512 "Reference target '{}' not found in spec '{}'",
1513 reference.name, target_spec_arc.name
1514 ),
1515 reference_source,
1516 ));
1517 None
1518 }
1519
1520 fn build_data_bindings(
1526 &mut self,
1527 spec: &LemmaSpec,
1528 current_segment_names: &[String],
1529 spec_arc: &Arc<LemmaSpec>,
1530 effective: &EffectiveDate,
1531 ) -> Result<DataBindings, Vec<Error>> {
1532 let mut bindings: DataBindings = HashMap::new();
1533 let mut errors: Vec<Error> = Vec::new();
1534
1535 for data in &spec.data {
1536 let has_binding_lhs_segments = !data.reference.segments.is_empty();
1537 let is_local_fill = matches!(&data.value, ParsedDataValue::Fill(_));
1538 if !has_binding_lhs_segments && !is_local_fill {
1539 continue;
1540 }
1541
1542 let binding_path_display = format!("{}", data.reference);
1543
1544 if matches!(&data.value, ParsedDataValue::Import(_)) {
1546 errors.push(self.engine_error(
1547 format!(
1548 "Data binding '{}' cannot override a spec reference — only literal values can be bound to nested data",
1549 binding_path_display
1550 ),
1551 &data.source_location,
1552 ));
1553 continue;
1554 }
1555
1556 if has_binding_lhs_segments {
1558 if let ParsedDataValue::Definition { .. } = &data.value {
1559 if !data.value.is_definition_literal_only() {
1560 errors.push(self.engine_error(
1561 format!(
1562 "Data binding '{}' must provide a literal value, not a data definition",
1563 binding_path_display
1564 ),
1565 &data.source_location,
1566 ));
1567 continue;
1568 }
1569 }
1570 }
1571
1572 if let Some((binding_key, binding_value, source)) =
1573 self.resolve_data_binding(data, current_segment_names, spec_arc, effective)
1574 {
1575 if let Some((_, existing_source)) = bindings.get(&binding_key) {
1576 errors.push(self.engine_error(
1577 format!(
1578 "Duplicate data binding for '{}' (previously bound at {}:{})",
1579 binding_key.join("."),
1580 existing_source.source_type,
1581 existing_source.span.line
1582 ),
1583 &data.source_location,
1584 ));
1585 } else {
1586 bindings.insert(binding_key, (binding_value, source));
1587 }
1588 }
1589 }
1591
1592 if !errors.is_empty() {
1593 return Err(errors);
1594 }
1595
1596 Ok(bindings)
1597 }
1598
1599 fn add_data(
1605 &mut self,
1606 data: &LemmaData,
1607 current_segments: &[PathSegment],
1608 data_bindings: &DataBindings,
1609 current_spec_arc: &Arc<LemmaSpec>,
1610 used_binding_keys: &mut HashSet<Vec<String>>,
1611 effective: &EffectiveDate,
1612 ) {
1613 let data_path = DataPath {
1614 segments: current_segments.to_vec(),
1615 data: data.reference.name.clone(),
1616 };
1617
1618 if let Some(existing) = self.data.get(&data_path) {
1620 let (message, suggestion) = if is_uses_vs_data_clash(existing, &data.value) {
1621 uses_vs_data_duplicate_message(&data_path.data, existing, &data.value)
1622 } else {
1623 (
1624 format!(
1625 "The name '{}' is already used for data in this spec.",
1626 data_path.data
1627 ),
1628 None,
1629 )
1630 };
1631 self.errors.push(Error::validation_with_context(
1632 message,
1633 Some(data.source_location.clone()),
1634 suggestion,
1635 Some(Arc::clone(&self.main_spec)),
1636 None,
1637 ));
1638 return;
1639 }
1640
1641 let binding_key: Vec<String> = current_segments
1643 .iter()
1644 .map(|s| s.data.clone())
1645 .chain(std::iter::once(data.reference.name.clone()))
1646 .collect();
1647
1648 let binding_override: Option<(BindingValue, Source)> =
1655 data_bindings.get(&binding_key).and_then(|(v, s)| {
1656 if matches!(v, BindingValue::Reference { .. })
1657 && Self::has_local_fill_reference_for_name(
1658 current_spec_arc.as_ref(),
1659 &data.reference.name,
1660 )
1661 {
1662 return None;
1663 }
1664 used_binding_keys.insert(binding_key.clone());
1665 Some((v.clone(), s.clone()))
1666 });
1667
1668 let (original_schema_type, original_declared_default) = if matches!(
1669 &data.value,
1670 ParsedDataValue::Definition { .. }
1671 ) && data
1672 .value
1673 .definition_needs_type_resolution()
1674 {
1675 let resolved = self
1676 .local_types
1677 .iter()
1678 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
1679 .map(|(_, _, t)| t)
1680 .expect("BUG: no resolved types for spec during add_local_data");
1681 let lemma_type = resolved
1682 .resolved
1683 .get(&data.reference.name)
1684 .expect("BUG: type not in ResolvedSpecTypes.resolved. TypeResolver should have registered it")
1685 .clone();
1686 let declared = resolved
1687 .declared_defaults
1688 .get(&data.reference.name)
1689 .cloned();
1690 (Some(lemma_type), declared)
1691 } else {
1692 (None, None)
1693 };
1694
1695 if let Some((binding_value, binding_source)) = binding_override {
1696 self.add_data_from_binding(
1697 data_path,
1698 binding_value,
1699 binding_source,
1700 original_schema_type,
1701 current_spec_arc,
1702 );
1703 return;
1704 }
1705
1706 let effective_source = data.source_location.clone();
1707
1708 match &data.value {
1709 ParsedDataValue::Definition { .. } if data.value.is_definition_literal_only() => {
1710 let ParsedDataValue::Definition {
1711 value: Some(value), ..
1712 } = &data.value
1713 else {
1714 unreachable!("BUG: literal-only Definition must carry value");
1715 };
1716 self.insert_literal_data(
1717 data_path,
1718 value,
1719 original_schema_type,
1720 effective_source,
1721 current_spec_arc,
1722 );
1723 }
1724 ParsedDataValue::Definition { .. } => {
1725 let mut resolved_type = original_schema_type.unwrap_or_else(|| {
1726 unreachable!(
1727 "BUG: Definition without schema — TypeResolver should have registered it"
1728 )
1729 });
1730 let mut declared_default = original_declared_default;
1731
1732 let is_generic_quantity_range = matches!(
1733 &resolved_type.specifications,
1734 TypeSpecification::QuantityRange {
1735 units,
1736 decomposition,
1737 canonical_unit,
1738 ..
1739 } if units.0.is_empty() && decomposition.is_empty() && canonical_unit.is_empty()
1740 );
1741
1742 if is_generic_quantity_range {
1743 if let Some(ValueKind::Range(left, right)) = &declared_default {
1744 if let (
1745 ValueKind::Quantity(_, left_unit, _),
1746 ValueKind::Quantity(_, right_unit, _),
1747 ) = (&left.value, &right.value)
1748 {
1749 let resolved = self
1750 .local_types
1751 .iter()
1752 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
1753 .map(|(_, _, t)| t)
1754 .expect("BUG: no resolved types for spec during add_local_data");
1755
1756 let left_quantity_type = resolved.unit_index.get(left_unit);
1757 let right_quantity_type = resolved.unit_index.get(right_unit);
1758
1759 match (left_quantity_type, right_quantity_type) {
1760 (Some(left_quantity_type), Some(right_quantity_type))
1761 if left_quantity_type
1762 .same_quantity_family(right_quantity_type) =>
1763 {
1764 let specialized_range_type =
1765 infer_range_type_from_endpoint_types(
1766 left_quantity_type,
1767 right_quantity_type,
1768 );
1769 let coerced_left = Graph::coerce_literal_to_schema_type(
1770 left.as_ref(),
1771 left_quantity_type,
1772 )
1773 .unwrap_or_else(|message| {
1774 unreachable!(
1775 "BUG: coercing quantity range default left endpoint failed: {}",
1776 message
1777 )
1778 });
1779 let coerced_right = Graph::coerce_literal_to_schema_type(
1780 right.as_ref(),
1781 right_quantity_type,
1782 )
1783 .unwrap_or_else(|message| {
1784 unreachable!(
1785 "BUG: coercing quantity range default right endpoint failed: {}",
1786 message
1787 )
1788 });
1789 let specialized_default = Graph::coerce_literal_to_schema_type(
1790 &LiteralValue {
1791 value: ValueKind::Range(
1792 Box::new(coerced_left),
1793 Box::new(coerced_right),
1794 ),
1795 lemma_type: specialized_range_type.clone(),
1796 },
1797 &specialized_range_type,
1798 )
1799 .unwrap_or_else(|message| {
1800 unreachable!(
1801 "BUG: specializing generic quantity range default failed: {}",
1802 message
1803 )
1804 });
1805 resolved_type = specialized_range_type;
1806 declared_default = Some(specialized_default.value);
1807 }
1808 _ => {
1809 self.errors.push(self.engine_error(
1810 format!(
1811 "Generic quantity range default must use units from one concrete local quantity family, got '{}' and '{}'",
1812 left_unit, right_unit
1813 ),
1814 &effective_source,
1815 ));
1816 return;
1817 }
1818 }
1819 }
1820 }
1821 }
1822
1823 self.data.insert(
1824 data_path,
1825 DataDefinition::TypeDeclaration {
1826 resolved_type,
1827 declared_default,
1828 source: effective_source,
1829 },
1830 );
1831 }
1832 ParsedDataValue::Import(spec_ref) => {
1833 let consumer_repository =
1834 discovery::lookup_owning_repository(self.context, current_spec_arc)
1835 .unwrap_or_else(|| Arc::clone(&self.main_repository));
1836 let effective_spec_arc = match self.resolve_spec_ref(
1837 spec_ref,
1838 effective,
1839 current_spec_arc,
1840 &consumer_repository,
1841 ) {
1842 Ok((_, arc)) => arc,
1843 Err(e) => {
1844 self.errors.push(e);
1845 return;
1846 }
1847 };
1848
1849 self.data.insert(
1850 data_path,
1851 DataDefinition::Import {
1852 spec: Arc::clone(&effective_spec_arc),
1853 source: effective_source,
1854 },
1855 );
1856 }
1857 ParsedDataValue::Fill(_) => {
1858 self.errors.push(self.engine_error(
1859 "Internal planning error: a fill row reached add_data; fill rows must apply only through data_bindings"
1860 .to_string(),
1861 &effective_source,
1862 ));
1863 }
1864 }
1865 }
1866
1867 fn insert_literal_data(
1871 &mut self,
1872 data_path: DataPath,
1873 value: &ast::Value,
1874 declared_schema_type: Option<LemmaType>,
1875 effective_source: Source,
1876 current_spec_arc: &Arc<LemmaSpec>,
1877 ) {
1878 let semantic_value = if let Some(ref schema) = declared_schema_type {
1879 match parser_value_to_value_kind(value, &schema.specifications) {
1880 Ok(s) => s,
1881 Err(e) => {
1882 self.errors.push(self.engine_error(e, &effective_source));
1883 return;
1884 }
1885 }
1886 } else {
1887 match value {
1888 Value::NumberWithUnit(magnitude, unit) => {
1889 let Some(lt) = self
1890 .local_types
1891 .iter()
1892 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
1893 .map(|(_, _, t)| t)
1894 .and_then(|dt| dt.unit_index.get(unit))
1895 else {
1896 self.errors.push(self.engine_error(
1897 format!("Unit '{}' is not in scope for this spec", unit),
1898 &effective_source,
1899 ));
1900 return;
1901 };
1902 match number_with_unit_to_value_kind(*magnitude, unit, lt) {
1903 Ok(s) => s,
1904 Err(e) => {
1905 self.errors.push(self.engine_error(e, &effective_source));
1906 return;
1907 }
1908 }
1909 }
1910 _ => match value_to_semantic(value) {
1911 Ok(s) => s,
1912 Err(e) => {
1913 self.errors.push(self.engine_error(e, &effective_source));
1914 return;
1915 }
1916 },
1917 }
1918 };
1919 let inferred_type = match value {
1920 Value::Text(_) => primitive_text().clone(),
1921 Value::Number(_) => primitive_number().clone(),
1922 Value::NumberWithUnit(_, unit) => {
1923 match self
1924 .local_types
1925 .iter()
1926 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
1927 .map(|(_, _, t)| t)
1928 .and_then(|dt| dt.unit_index.get(unit))
1929 {
1930 Some(lt) => lt.clone(),
1931 None => {
1932 self.errors.push(self.engine_error(
1933 format!("Unit '{}' is not in scope for this spec", unit),
1934 &effective_source,
1935 ));
1936 return;
1937 }
1938 }
1939 }
1940 Value::Boolean(_) => primitive_boolean().clone(),
1941 Value::Date(_) => primitive_date().clone(),
1942 Value::Time(_) => primitive_time().clone(),
1943 Value::Calendar(_, _) => primitive_calendar().clone(),
1944 Value::Range(_, _) => match &semantic_value {
1945 ValueKind::Range(left, right) => {
1946 LiteralValue::range(left.as_ref().clone(), right.as_ref().clone()).lemma_type
1947 }
1948 _ => unreachable!(
1949 "BUG: semantic range literal conversion returned non-range value kind"
1950 ),
1951 },
1952 };
1953 let schema_type = declared_schema_type.unwrap_or(inferred_type);
1954 let literal_value = LiteralValue {
1955 value: semantic_value,
1956 lemma_type: schema_type,
1957 };
1958 self.data.insert(
1959 data_path,
1960 DataDefinition::Value {
1961 value: literal_value,
1962 source: effective_source,
1963 },
1964 );
1965 }
1966
1967 fn add_data_from_binding(
1970 &mut self,
1971 data_path: DataPath,
1972 binding_value: BindingValue,
1973 binding_source: Source,
1974 declared_schema_type: Option<LemmaType>,
1975 current_spec_arc: &Arc<LemmaSpec>,
1976 ) {
1977 match binding_value {
1978 BindingValue::Literal(value) => {
1979 self.insert_literal_data(
1980 data_path,
1981 &value,
1982 declared_schema_type,
1983 binding_source,
1984 current_spec_arc,
1985 );
1986 }
1987 BindingValue::Reference {
1988 target,
1989 constraints,
1990 } => {
1991 let provisional_type =
1992 declared_schema_type.unwrap_or_else(LemmaType::undetermined_type);
1993 self.data.insert(
1994 data_path,
1995 DataDefinition::Reference {
1996 target,
1997 resolved_type: provisional_type,
1998 local_constraints: constraints,
1999 local_default: None,
2000 source: binding_source,
2001 },
2002 );
2003 }
2004 }
2005 }
2006
2007 fn resolve_path_segments(
2009 &mut self,
2010 segments: &[String],
2011 reference_source: &Source,
2012 mut current_data_map: HashMap<String, LemmaData>,
2013 mut path_segments: Vec<PathSegment>,
2014 mut spec_context: Arc<LemmaSpec>,
2015 effective: &EffectiveDate,
2016 ) -> Option<(Vec<PathSegment>, Arc<LemmaSpec>)> {
2017 let mut last_arc: Option<Arc<LemmaSpec>> = None;
2018
2019 for segment in segments.iter() {
2020 let data_ref =
2021 match current_data_map.get(segment) {
2022 Some(f) => f,
2023 None => {
2024 self.errors.push(self.engine_error(
2025 format!("Data '{}' not found", segment),
2026 reference_source,
2027 ));
2028 return None;
2029 }
2030 };
2031
2032 if let ParsedDataValue::Import(original_spec_ref) = &data_ref.value {
2033 let context_repository =
2034 discovery::lookup_owning_repository(self.context, &spec_context)
2035 .unwrap_or_else(|| Arc::clone(&self.main_repository));
2036 let arc = match self.resolve_spec_ref(
2037 original_spec_ref,
2038 effective,
2039 &spec_context,
2040 &context_repository,
2041 ) {
2042 Ok((_, a)) => a,
2043 Err(e) => {
2044 self.errors.push(e);
2045 return None;
2046 }
2047 };
2048 spec_context = Arc::clone(&arc);
2049
2050 path_segments.push(PathSegment {
2051 data: segment.clone(),
2052 spec: arc.name.clone(),
2053 });
2054 current_data_map = arc
2055 .data
2056 .iter()
2057 .map(|f| (f.reference.name.clone(), f.clone()))
2058 .collect();
2059 last_arc = Some(arc);
2060 } else {
2061 self.errors.push(self.engine_error(
2062 format!("Data '{}' is not a spec reference", segment),
2063 reference_source,
2064 ));
2065 return None;
2066 }
2067 }
2068
2069 let final_arc = last_arc.unwrap_or_else(|| {
2070 unreachable!(
2071 "BUG: resolve_path_segments called with empty segments should not reach here"
2072 )
2073 });
2074 Some((path_segments, final_arc))
2075 }
2076
2077 fn has_local_fill_reference_for_name(spec: &LemmaSpec, name: &str) -> bool {
2078 spec.data.iter().any(|d| {
2079 d.reference.segments.is_empty()
2080 && d.reference.name == name
2081 && matches!(&d.value, ParsedDataValue::Fill(FillRhs::Reference { .. }))
2082 })
2083 }
2084
2085 fn materialize_local_fill_rows(
2088 &mut self,
2089 spec: &LemmaSpec,
2090 current_segments: &[PathSegment],
2091 effective_bindings: &DataBindings,
2092 spec_arc: &Arc<LemmaSpec>,
2093 used_binding_keys: &mut HashSet<Vec<String>>,
2094 ) {
2095 let current_segment_names: Vec<String> =
2096 current_segments.iter().map(|s| s.data.clone()).collect();
2097
2098 for data in &spec.data {
2099 if !data.reference.segments.is_empty() {
2100 continue;
2101 }
2102 if !matches!(&data.value, ParsedDataValue::Fill(_)) {
2103 continue;
2104 }
2105
2106 let data_path = DataPath {
2107 segments: current_segments.to_vec(),
2108 data: data.reference.name.clone(),
2109 };
2110
2111 let binding_key: Vec<String> = current_segment_names
2112 .iter()
2113 .cloned()
2114 .chain(std::iter::once(data.reference.name.clone()))
2115 .collect();
2116
2117 let Some((binding_value, binding_source)) = effective_bindings.get(&binding_key) else {
2118 self.errors.push(self.engine_error(
2119 format!(
2120 "Internal planning error: fill '{}' has no resolved binding",
2121 data.reference.name
2122 ),
2123 &data.source_location,
2124 ));
2125 continue;
2126 };
2127
2128 used_binding_keys.insert(binding_key);
2129
2130 if let Some(DataDefinition::TypeDeclaration {
2131 resolved_type,
2132 declared_default,
2133 ..
2134 }) = self.data.get(&data_path)
2135 {
2136 let BindingValue::Reference {
2137 target,
2138 constraints,
2139 } = binding_value
2140 else {
2141 continue;
2142 };
2143 if constraints.is_some() {
2144 self.errors.push(self.engine_error(
2145 format!(
2146 "Constraint chains (`-> ...`) on `fill` are not allowed; use `data {}: … -> …` for constraints",
2147 data.reference.name
2148 ),
2149 &data.source_location,
2150 ));
2151 continue;
2152 }
2153 let resolved_type = resolved_type.clone();
2154 let declared_default = declared_default.clone();
2155 self.data.insert(
2156 data_path.clone(),
2157 DataDefinition::Reference {
2158 target: target.clone(),
2159 resolved_type,
2160 local_constraints: None,
2161 local_default: declared_default,
2162 source: binding_source.clone(),
2163 },
2164 );
2165 continue;
2166 }
2167
2168 if self.data.contains_key(&data_path) {
2169 continue;
2170 }
2171
2172 self.add_data_from_binding(
2173 data_path,
2174 binding_value.clone(),
2175 binding_source.clone(),
2176 None,
2177 spec_arc,
2178 );
2179 }
2180 }
2181
2182 fn build_spec(
2183 &mut self,
2184 spec_arc: &Arc<LemmaSpec>,
2185 spec_repository: &Arc<LemmaRepository>,
2186 current_segments: Vec<PathSegment>,
2187 data_bindings: DataBindings,
2188 effective: &EffectiveDate,
2189 type_resolver: &mut TypeResolver<'a>,
2190 ) -> Result<(), Vec<Error>> {
2191 let spec = spec_arc.as_ref();
2192
2193 if current_segments.is_empty() {
2194 self.process_meta_fields(spec);
2195 }
2196
2197 let current_segment_names: Vec<String> =
2198 current_segments.iter().map(|s| s.data.clone()).collect();
2199
2200 let this_spec_bindings =
2202 match self.build_data_bindings(spec, ¤t_segment_names, spec_arc, effective) {
2203 Ok(bindings) => bindings,
2204 Err(errors) => {
2205 self.errors.extend(errors);
2206 HashMap::new()
2207 }
2208 };
2209
2210 let data_map: HashMap<String, &LemmaData> = spec
2212 .data
2213 .iter()
2214 .map(|data| (data.reference.name.clone(), data))
2215 .collect();
2216
2217 if !self
2218 .local_types
2219 .iter()
2220 .any(|(_, s, _)| Arc::ptr_eq(s, spec_arc))
2221 {
2222 if !type_resolver.is_registered(spec_arc) {
2226 return Ok(());
2227 }
2228 match type_resolver.resolve_and_validate(spec_arc, effective) {
2229 Ok(resolved_types) => {
2230 self.local_types.push((
2231 Arc::clone(spec_repository),
2232 Arc::clone(spec_arc),
2233 resolved_types,
2234 ));
2235 }
2236 Err(es) => {
2237 self.errors.extend(es);
2238 return Ok(());
2239 }
2240 }
2241 }
2242
2243 for data in &spec.data {
2244 if let ParsedDataValue::Definition {
2245 base: Some(ParentType::Qualified { spec_alias, .. }),
2246 ..
2247 } = &data.value
2248 {
2249 let from_ref = ast::SpecRef::same_repository(spec_alias.clone());
2250 match self.resolve_spec_ref(&from_ref, effective, spec_arc, spec_repository) {
2251 Ok((source_repo, source_arc)) => {
2252 if !self
2253 .local_types
2254 .iter()
2255 .any(|(_, s, _)| Arc::ptr_eq(s, &source_arc))
2256 {
2257 match type_resolver.resolve_and_validate(&source_arc, effective) {
2258 Ok(resolved_types) => {
2259 self.local_types.push((
2260 source_repo,
2261 source_arc,
2262 resolved_types,
2263 ));
2264 }
2265 Err(es) => self.errors.extend(es),
2266 }
2267 }
2268 }
2269 Err(e) => self.errors.push(e),
2270 }
2271 }
2272 }
2273
2274 let mut effective_bindings = data_bindings.clone();
2275 effective_bindings.extend(this_spec_bindings.clone());
2276
2277 let mut used_binding_keys: HashSet<Vec<String>> = HashSet::new();
2279 for data in &spec.data {
2280 if !data.reference.segments.is_empty() {
2281 continue; }
2283 if matches!(&data.value, ParsedDataValue::Fill(_)) {
2284 continue; }
2286 if matches!(&data.value, ParsedDataValue::Import(_)) {
2287 continue;
2288 }
2289 self.add_data(
2290 data,
2291 ¤t_segments,
2292 &effective_bindings,
2293 spec_arc,
2294 &mut used_binding_keys,
2295 effective,
2296 );
2297 }
2298
2299 self.materialize_local_fill_rows(
2300 spec,
2301 ¤t_segments,
2302 &effective_bindings,
2303 spec_arc,
2304 &mut used_binding_keys,
2305 );
2306
2307 for data in &spec.data {
2308 if !data.reference.segments.is_empty() {
2309 continue;
2310 }
2311 if let ParsedDataValue::Import(spec_ref) = &data.value {
2312 let nested_effective = spec_ref.at(effective);
2313 let (nested_repo, nested_arc) =
2314 match self.resolve_spec_ref(spec_ref, effective, spec_arc, spec_repository) {
2315 Ok(pair) => pair,
2316 Err(e) => {
2317 self.errors.push(e);
2318 continue;
2319 }
2320 };
2321 self.add_data(
2322 data,
2323 ¤t_segments,
2324 &effective_bindings,
2325 spec_arc,
2326 &mut used_binding_keys,
2327 effective,
2328 );
2329 let mut nested_segments = current_segments.clone();
2330 nested_segments.push(PathSegment {
2331 data: data.reference.name.clone(),
2332 spec: nested_arc.name.clone(),
2333 });
2334
2335 let nested_segment_names: Vec<String> =
2336 nested_segments.iter().map(|s| s.data.clone()).collect();
2337 let mut combined_bindings = effective_bindings.clone();
2338 for (key, value_and_source) in &data_bindings {
2339 if key.len() > nested_segment_names.len()
2340 && key[..nested_segment_names.len()] == nested_segment_names[..]
2341 && !combined_bindings.contains_key(key)
2342 {
2343 combined_bindings.insert(key.clone(), value_and_source.clone());
2344 }
2345 }
2346
2347 if let Err(errs) = self.build_spec(
2348 &nested_arc,
2349 &nested_repo,
2350 nested_segments,
2351 combined_bindings,
2352 &nested_effective,
2353 type_resolver,
2354 ) {
2355 self.errors.extend(errs);
2356 }
2357 }
2358 }
2359
2360 let expected_key_len = current_segments.len() + 1;
2362 for data in &spec.data {
2363 if data.reference.segments.is_empty() {
2364 continue;
2365 }
2366 let mut binding_key: Vec<String> = current_segment_names.clone();
2367 binding_key.extend(data.reference.segments.iter().cloned());
2368 binding_key.push(data.reference.name.clone());
2369 if binding_key.len() != expected_key_len {
2370 continue;
2371 }
2372 if used_binding_keys.contains(&binding_key) {
2373 continue;
2374 }
2375 let Some((_, binding_source)) = effective_bindings.get(&binding_key) else {
2376 continue;
2377 };
2378 self.errors.push(self.engine_error(
2379 format!(
2380 "No declared data matches fill or binding for '{}'",
2381 binding_key.join(".")
2382 ),
2383 binding_source,
2384 ));
2385 }
2386
2387 let rule_names: HashSet<&str> = spec.rules.iter().map(|r| r.name.as_str()).collect();
2388 for rule in &spec.rules {
2389 self.add_rule(
2390 rule,
2391 spec_arc,
2392 &data_map,
2393 ¤t_segments,
2394 &rule_names,
2395 effective,
2396 );
2397 }
2398
2399 Ok(())
2400 }
2401
2402 fn add_rule(
2403 &mut self,
2404 rule: &LemmaRule,
2405 current_spec_arc: &Arc<LemmaSpec>,
2406 data_map: &HashMap<String, &LemmaData>,
2407 current_segments: &[PathSegment],
2408 rule_names: &HashSet<&str>,
2409 effective: &EffectiveDate,
2410 ) {
2411 let rule_path = RulePath {
2412 segments: current_segments.to_vec(),
2413 rule: rule.name.clone(),
2414 };
2415
2416 if self.rules.contains_key(&rule_path) {
2417 let rule_source = &rule.source_location;
2418 self.errors.push(
2419 self.engine_error(format!("Duplicate rule '{}'", rule_path.rule), rule_source),
2420 );
2421 return;
2422 }
2423
2424 let mut branches = Vec::new();
2425 let mut depends_on_rules = BTreeSet::new();
2426
2427 let converted_expression = match self.convert_expression_and_extract_dependencies(
2428 &rule.expression,
2429 current_spec_arc,
2430 data_map,
2431 current_segments,
2432 &mut depends_on_rules,
2433 rule_names,
2434 effective,
2435 ) {
2436 Some(expr) => expr,
2437 None => return,
2438 };
2439 branches.push((None, converted_expression));
2440
2441 for unless_clause in &rule.unless_clauses {
2442 let converted_condition = match self.convert_expression_and_extract_dependencies(
2443 &unless_clause.condition,
2444 current_spec_arc,
2445 data_map,
2446 current_segments,
2447 &mut depends_on_rules,
2448 rule_names,
2449 effective,
2450 ) {
2451 Some(expr) => expr,
2452 None => return,
2453 };
2454 let converted_result = match self.convert_expression_and_extract_dependencies(
2455 &unless_clause.result,
2456 current_spec_arc,
2457 data_map,
2458 current_segments,
2459 &mut depends_on_rules,
2460 rule_names,
2461 effective,
2462 ) {
2463 Some(expr) => expr,
2464 None => return,
2465 };
2466 branches.push((Some(converted_condition), converted_result));
2467 }
2468
2469 let rule_node = RuleNode {
2470 branches,
2471 source: rule.source_location.clone(),
2472 depends_on_rules,
2473 rule_type: LemmaType::veto_type(),
2474 spec_arc: Arc::clone(current_spec_arc),
2475 };
2476
2477 self.rules.insert(rule_path, rule_node);
2478 }
2479
2480 #[allow(clippy::too_many_arguments)]
2482 fn convert_binary_operands(
2483 &mut self,
2484 left: &ast::Expression,
2485 right: &ast::Expression,
2486 current_spec_arc: &Arc<LemmaSpec>,
2487 data_map: &HashMap<String, &LemmaData>,
2488 current_segments: &[PathSegment],
2489 depends_on_rules: &mut BTreeSet<RulePath>,
2490 rule_names: &HashSet<&str>,
2491 effective: &EffectiveDate,
2492 ) -> Option<(Expression, Expression)> {
2493 let converted_left = self.convert_expression_and_extract_dependencies(
2494 left,
2495 current_spec_arc,
2496 data_map,
2497 current_segments,
2498 depends_on_rules,
2499 rule_names,
2500 effective,
2501 )?;
2502 let converted_right = self.convert_expression_and_extract_dependencies(
2503 right,
2504 current_spec_arc,
2505 data_map,
2506 current_segments,
2507 depends_on_rules,
2508 rule_names,
2509 effective,
2510 )?;
2511 Some((converted_left, converted_right))
2512 }
2513
2514 #[allow(clippy::too_many_arguments)]
2516 fn convert_expression_and_extract_dependencies(
2517 &mut self,
2518 expr: &ast::Expression,
2519 current_spec_arc: &Arc<LemmaSpec>,
2520 data_map: &HashMap<String, &LemmaData>,
2521 current_segments: &[PathSegment],
2522 depends_on_rules: &mut BTreeSet<RulePath>,
2523 rule_names: &HashSet<&str>,
2524 effective: &EffectiveDate,
2525 ) -> Option<Expression> {
2526 let expr_src = expr
2527 .source_location
2528 .as_ref()
2529 .expect("BUG: AST expression missing source location");
2530 match &expr.kind {
2531 ast::ExpressionKind::Reference(r) => {
2532 let expr_source = expr_src;
2533 let (segments, target_arc_opt) = if r.segments.is_empty() {
2534 (current_segments.to_vec(), None)
2535 } else {
2536 let data_map_owned: HashMap<String, LemmaData> = data_map
2537 .iter()
2538 .map(|(k, v)| (k.clone(), (*v).clone()))
2539 .collect();
2540 let (segs, arc) = self.resolve_path_segments(
2541 &r.segments,
2542 expr_source,
2543 data_map_owned,
2544 current_segments.to_vec(),
2545 Arc::clone(current_spec_arc),
2546 effective,
2547 )?;
2548 (segs, Some(arc))
2549 };
2550
2551 let (is_data, is_rule, target_spec_name_opt) = match &target_arc_opt {
2552 None => {
2553 let is_data = data_map.contains_key(&r.name);
2554 let is_rule = rule_names.contains(r.name.as_str());
2555 (is_data, is_rule, None)
2556 }
2557 Some(target_arc) => {
2558 let target_spec = target_arc.as_ref();
2559 let target_data_names: HashSet<&str> = target_spec
2560 .data
2561 .iter()
2562 .filter(|f| f.reference.is_local())
2563 .map(|f| f.reference.name.as_str())
2564 .collect();
2565 let target_rule_names: HashSet<&str> =
2566 target_spec.rules.iter().map(|r| r.name.as_str()).collect();
2567 let is_data = target_data_names.contains(r.name.as_str());
2568 let is_rule = target_rule_names.contains(r.name.as_str());
2569 (is_data, is_rule, Some(target_spec.name.as_str()))
2570 }
2571 };
2572
2573 if is_data && is_rule {
2574 self.errors.push(self.engine_error(
2575 format!("'{}' is both a data and a rule", r.name),
2576 expr_source,
2577 ));
2578 return None;
2579 }
2580 if is_data {
2581 let data_path = DataPath {
2582 segments,
2583 data: r.name.clone(),
2584 };
2585 return Some(Expression {
2586 kind: ExpressionKind::DataPath(data_path),
2587 source_location: expr.source_location.clone(),
2588 });
2589 }
2590 if is_rule {
2591 let rule_path = RulePath {
2592 segments,
2593 rule: r.name.clone(),
2594 };
2595 depends_on_rules.insert(rule_path.clone());
2596 return Some(Expression {
2597 kind: ExpressionKind::RulePath(rule_path),
2598 source_location: expr.source_location.clone(),
2599 });
2600 }
2601 let msg = match target_spec_name_opt {
2602 Some(s) => format!("Reference '{}' not found in spec '{}'", r.name, s),
2603 None => format!("Reference '{}' not found", r.name),
2604 };
2605 self.errors.push(self.engine_error(msg, expr_source));
2606 None
2607 }
2608
2609 ast::ExpressionKind::LogicalAnd(left, right) => {
2610 let (l, r) = self.convert_binary_operands(
2611 left,
2612 right,
2613 current_spec_arc,
2614 data_map,
2615 current_segments,
2616 depends_on_rules,
2617 rule_names,
2618 effective,
2619 )?;
2620 Some(Expression {
2621 kind: ExpressionKind::LogicalAnd(Arc::new(l), Arc::new(r)),
2622 source_location: expr.source_location.clone(),
2623 })
2624 }
2625
2626 ast::ExpressionKind::Arithmetic(left, op, right) => {
2627 let (l, r) = self.convert_binary_operands(
2628 left,
2629 right,
2630 current_spec_arc,
2631 data_map,
2632 current_segments,
2633 depends_on_rules,
2634 rule_names,
2635 effective,
2636 )?;
2637 Some(Expression {
2638 kind: ExpressionKind::Arithmetic(Arc::new(l), op.clone(), Arc::new(r)),
2639 source_location: expr.source_location.clone(),
2640 })
2641 }
2642
2643 ast::ExpressionKind::Comparison(left, op, right) => {
2644 let (l, r) = self.convert_binary_operands(
2645 left,
2646 right,
2647 current_spec_arc,
2648 data_map,
2649 current_segments,
2650 depends_on_rules,
2651 rule_names,
2652 effective,
2653 )?;
2654 Some(Expression {
2655 kind: ExpressionKind::Comparison(Arc::new(l), op.clone(), Arc::new(r)),
2656 source_location: expr.source_location.clone(),
2657 })
2658 }
2659
2660 ast::ExpressionKind::UnitConversion(value, target) => {
2661 let converted_value = self.convert_expression_and_extract_dependencies(
2662 value,
2663 current_spec_arc,
2664 data_map,
2665 current_segments,
2666 depends_on_rules,
2667 rule_names,
2668 effective,
2669 )?;
2670
2671 let resolved_spec_types = self
2672 .local_types
2673 .iter()
2674 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
2675 .map(|(_, _, t)| t);
2676 let unit_index = resolved_spec_types.map(|dt| &dt.unit_index);
2677 let semantic_target = match conversion_target_to_semantic(target, unit_index) {
2678 Ok(t) => t,
2679 Err(msg) => {
2680 let full_msg = unit_index
2683 .map(|idx| {
2684 let valid: Vec<&str> = idx.keys().map(String::as_str).collect();
2685 format!("{} Valid units: {}", msg, valid.join(", "))
2686 })
2687 .unwrap_or(msg);
2688 self.errors.push(Error::validation_with_context(
2689 full_msg,
2690 expr.source_location.clone(),
2691 None::<String>,
2692 Some(Arc::clone(&self.main_spec)),
2693 None,
2694 ));
2695 return None;
2696 }
2697 };
2698
2699 Some(Expression {
2700 kind: ExpressionKind::UnitConversion(
2701 Arc::new(converted_value),
2702 semantic_target,
2703 ),
2704 source_location: expr.source_location.clone(),
2705 })
2706 }
2707
2708 ast::ExpressionKind::LogicalNegation(operand, neg_type) => {
2709 let converted_operand = self.convert_expression_and_extract_dependencies(
2710 operand,
2711 current_spec_arc,
2712 data_map,
2713 current_segments,
2714 depends_on_rules,
2715 rule_names,
2716 effective,
2717 )?;
2718 Some(Expression {
2719 kind: ExpressionKind::LogicalNegation(
2720 Arc::new(converted_operand),
2721 neg_type.clone(),
2722 ),
2723 source_location: expr.source_location.clone(),
2724 })
2725 }
2726
2727 ast::ExpressionKind::MathematicalComputation(op, operand) => {
2728 let converted_operand = self.convert_expression_and_extract_dependencies(
2729 operand,
2730 current_spec_arc,
2731 data_map,
2732 current_segments,
2733 depends_on_rules,
2734 rule_names,
2735 effective,
2736 )?;
2737 Some(Expression {
2738 kind: ExpressionKind::MathematicalComputation(
2739 op.clone(),
2740 Arc::new(converted_operand),
2741 ),
2742 source_location: expr.source_location.clone(),
2743 })
2744 }
2745
2746 ast::ExpressionKind::Literal(value) => {
2747 let semantic_value = match value {
2748 Value::NumberWithUnit(magnitude, unit) => {
2749 let Some(lt) = self
2750 .local_types
2751 .iter()
2752 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
2753 .map(|(_, _, t)| t)
2754 .and_then(|dt| dt.unit_index.get(unit))
2755 else {
2756 self.errors.push(self.engine_error(
2757 format!("Unit '{}' is not in scope for this spec", unit),
2758 expr_src,
2759 ));
2760 return None;
2761 };
2762 match number_with_unit_to_value_kind(*magnitude, unit, lt) {
2763 Ok(v) => v,
2764 Err(e) => {
2765 self.errors.push(self.engine_error(e, expr_src));
2766 return None;
2767 }
2768 }
2769 }
2770 _ => match value_to_semantic(value) {
2771 Ok(v) => v,
2772 Err(e) => {
2773 self.errors.push(self.engine_error(e, expr_src));
2774 return None;
2775 }
2776 },
2777 };
2778 let lemma_type = match value {
2779 Value::Text(_) => primitive_text().clone(),
2780 Value::Number(_) => primitive_number().clone(),
2781 Value::NumberWithUnit(_, unit) => {
2782 match self
2783 .local_types
2784 .iter()
2785 .find(|(_, s, _)| Arc::ptr_eq(s, current_spec_arc))
2786 .map(|(_, _, t)| t)
2787 .and_then(|dt| dt.unit_index.get(unit))
2788 {
2789 Some(lt) => lt.clone(),
2790 None => {
2791 self.errors.push(self.engine_error(
2792 format!("Unit '{}' is not in scope for this spec", unit),
2793 expr_src,
2794 ));
2795 return None;
2796 }
2797 }
2798 }
2799 Value::Boolean(_) => primitive_boolean().clone(),
2800 Value::Date(_) => primitive_date().clone(),
2801 Value::Time(_) => primitive_time().clone(),
2802 Value::Calendar(_, _) => primitive_calendar().clone(),
2803 Value::Range(_, _) => match &semantic_value {
2804 ValueKind::Range(left, right) => {
2805 LiteralValue::range(left.as_ref().clone(), right.as_ref().clone())
2806 .lemma_type
2807 }
2808 _ => unreachable!(
2809 "BUG: semantic range literal conversion returned non-range value kind"
2810 ),
2811 },
2812 };
2813 let literal_value = LiteralValue {
2814 value: semantic_value,
2815 lemma_type,
2816 };
2817 Some(Expression {
2818 kind: ExpressionKind::Literal(Box::new(literal_value)),
2819 source_location: expr.source_location.clone(),
2820 })
2821 }
2822
2823 ast::ExpressionKind::Veto(veto_expression) => Some(Expression {
2824 kind: ExpressionKind::Veto(veto_expression.clone()),
2825 source_location: expr.source_location.clone(),
2826 }),
2827
2828 ast::ExpressionKind::ResultIsVeto(operand) => {
2829 let converted = self.convert_expression_and_extract_dependencies(
2830 operand,
2831 current_spec_arc,
2832 data_map,
2833 current_segments,
2834 depends_on_rules,
2835 rule_names,
2836 effective,
2837 )?;
2838 Some(Expression {
2839 kind: ExpressionKind::ResultIsVeto(Arc::new(converted)),
2840 source_location: expr.source_location.clone(),
2841 })
2842 }
2843
2844 ast::ExpressionKind::Now => Some(Expression {
2845 kind: ExpressionKind::Now,
2846 source_location: expr.source_location.clone(),
2847 }),
2848
2849 ast::ExpressionKind::DateRelative(kind, date_expr) => {
2850 let converted_date = self.convert_expression_and_extract_dependencies(
2851 date_expr,
2852 current_spec_arc,
2853 data_map,
2854 current_segments,
2855 depends_on_rules,
2856 rule_names,
2857 effective,
2858 )?;
2859 Some(Expression {
2860 kind: ExpressionKind::DateRelative(*kind, Arc::new(converted_date)),
2861 source_location: expr.source_location.clone(),
2862 })
2863 }
2864
2865 ast::ExpressionKind::DateCalendar(kind, unit, date_expr) => {
2866 let converted_date = self.convert_expression_and_extract_dependencies(
2867 date_expr,
2868 current_spec_arc,
2869 data_map,
2870 current_segments,
2871 depends_on_rules,
2872 rule_names,
2873 effective,
2874 )?;
2875 Some(Expression {
2876 kind: ExpressionKind::DateCalendar(*kind, *unit, Arc::new(converted_date)),
2877 source_location: expr.source_location.clone(),
2878 })
2879 }
2880
2881 ast::ExpressionKind::RangeLiteral(left, right) => {
2882 let (l, r) = self.convert_binary_operands(
2883 left,
2884 right,
2885 current_spec_arc,
2886 data_map,
2887 current_segments,
2888 depends_on_rules,
2889 rule_names,
2890 effective,
2891 )?;
2892 Some(Expression {
2893 kind: ExpressionKind::RangeLiteral(Arc::new(l), Arc::new(r)),
2894 source_location: expr.source_location.clone(),
2895 })
2896 }
2897
2898 ast::ExpressionKind::PastFutureRange(kind, offset_expr) => {
2899 let converted_offset = self.convert_expression_and_extract_dependencies(
2900 offset_expr,
2901 current_spec_arc,
2902 data_map,
2903 current_segments,
2904 depends_on_rules,
2905 rule_names,
2906 effective,
2907 )?;
2908 Some(Expression {
2909 kind: ExpressionKind::PastFutureRange(*kind, Arc::new(converted_offset)),
2910 source_location: expr.source_location.clone(),
2911 })
2912 }
2913
2914 ast::ExpressionKind::RangeContainment(value, range) => {
2915 let (converted_value, converted_range) = self.convert_binary_operands(
2916 value,
2917 range,
2918 current_spec_arc,
2919 data_map,
2920 current_segments,
2921 depends_on_rules,
2922 rule_names,
2923 effective,
2924 )?;
2925 Some(Expression {
2926 kind: ExpressionKind::RangeContainment(
2927 Arc::new(converted_value),
2928 Arc::new(converted_range),
2929 ),
2930 source_location: expr.source_location.clone(),
2931 })
2932 }
2933 }
2934 }
2935}
2936
2937fn find_types_by_spec<'b>(
2940 types: &'b ResolvedTypesMap,
2941 spec_arc: &Arc<LemmaSpec>,
2942) -> Option<&'b ResolvedSpecTypes> {
2943 types
2944 .iter()
2945 .find(|(_, s, _)| Arc::ptr_eq(s, spec_arc))
2946 .map(|(_, _, t)| t)
2947}
2948
2949fn find_duration_type_in_spec(
2950 resolved_types: &ResolvedTypesMap,
2951 spec_arc: &Arc<LemmaSpec>,
2952) -> Option<LemmaType> {
2953 let spec_types = find_types_by_spec(resolved_types, spec_arc)?;
2954 if let Some(named) = spec_types
2955 .resolved
2956 .values()
2957 .find(|lemma_type| lemma_type.is_duration_like_quantity())
2958 {
2959 return Some(named.clone());
2960 }
2961 if let Some(from_units) = spec_types
2962 .unit_index
2963 .values()
2964 .find(|lemma_type| lemma_type.is_duration_like_quantity())
2965 {
2966 return Some(from_units.clone());
2967 }
2968 for data in &spec_arc.data {
2969 let ParsedDataValue::Import(spec_ref) = &data.value else {
2970 continue;
2971 };
2972 let (_, _, imported_types) = resolved_types
2973 .iter()
2974 .find(|(_, s, _)| s.name == spec_ref.name)?;
2975 if let Some(duration_type) = imported_types
2976 .resolved
2977 .get("duration")
2978 .filter(|t| t.is_duration_like_quantity())
2979 {
2980 return Some(duration_type.clone());
2981 }
2982 }
2983 None
2984}
2985
2986fn compute_arithmetic_result_type(
2987 left_type: LemmaType,
2988 op: &ArithmeticComputation,
2989 right_type: LemmaType,
2990) -> LemmaType {
2991 compute_arithmetic_result_type_recursive(left_type, op, right_type, false)
2992}
2993
2994fn compute_arithmetic_result_type_recursive(
2995 left_type: LemmaType,
2996 op: &ArithmeticComputation,
2997 right_type: LemmaType,
2998 swapped: bool,
2999) -> LemmaType {
3000 match (&left_type.specifications, &right_type.specifications) {
3001 (TypeSpecification::Veto { .. }, _) | (_, TypeSpecification::Veto { .. }) => {
3002 LemmaType::veto_type()
3003 }
3004 (TypeSpecification::Undetermined, _) => LemmaType::undetermined_type(),
3005
3006 (TypeSpecification::Date { .. }, TypeSpecification::Date { .. }) => {
3007 LemmaType::undetermined_type()
3008 }
3009 (TypeSpecification::Date { .. }, TypeSpecification::Time { .. }) => {
3010 LemmaType::anonymous_for_decomposition(duration_decomposition())
3011 }
3012 (TypeSpecification::Time { .. }, TypeSpecification::Time { .. }) => {
3013 LemmaType::anonymous_for_decomposition(duration_decomposition())
3014 }
3015
3016 _ if left_type == right_type
3019 && !matches!(
3020 &left_type.specifications,
3021 TypeSpecification::Quantity { .. }
3022 | TypeSpecification::QuantityRange { .. }
3023 | TypeSpecification::NumberRange { .. }
3024 | TypeSpecification::DateRange { .. }
3025 | TypeSpecification::Calendar { .. }
3026 | TypeSpecification::RatioRange { .. }
3027 ) =>
3028 {
3029 left_type
3030 }
3031
3032 (TypeSpecification::Date { .. }, TypeSpecification::Calendar { .. }) => left_type,
3033 (TypeSpecification::Date { .. }, TypeSpecification::Quantity { .. })
3034 if right_type.is_duration_like_quantity() =>
3035 {
3036 left_type
3037 }
3038 (TypeSpecification::Time { .. }, TypeSpecification::Quantity { .. })
3039 if right_type.is_duration_like_quantity() =>
3040 {
3041 left_type
3042 }
3043
3044 (TypeSpecification::Quantity { .. }, TypeSpecification::Ratio { .. }) => left_type,
3045 (TypeSpecification::Quantity { .. }, TypeSpecification::Number { .. }) => left_type,
3046 (
3047 TypeSpecification::Quantity {
3048 decomposition: l_decomp,
3049 ..
3050 },
3051 TypeSpecification::Calendar { .. },
3052 ) => match op {
3053 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3054 LemmaType::undetermined_type()
3055 }
3056 ArithmeticComputation::Multiply | ArithmeticComputation::Divide => {
3057 let cal_decomp = calendar_decomposition();
3058 let combined = combine_decompositions(
3059 l_decomp,
3060 &cal_decomp,
3061 matches!(op, ArithmeticComputation::Multiply),
3062 );
3063 if combined.is_empty() {
3064 primitive_number().clone()
3065 } else {
3066 LemmaType::anonymous_for_decomposition(combined)
3067 }
3068 }
3069 _ => primitive_number().clone(),
3070 },
3071 (
3072 TypeSpecification::Quantity {
3073 decomposition: l_decomp,
3074 ..
3075 },
3076 TypeSpecification::Quantity {
3077 decomposition: r_decomp,
3078 ..
3079 },
3080 ) => match op {
3081 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3082 if left_type.compatible_with_anonymous_quantity(&right_type)
3083 || right_type.compatible_with_anonymous_quantity(&left_type)
3084 {
3085 let left_decomp = left_type.quantity_type_decomposition();
3086 let right_decomp = right_type.quantity_type_decomposition();
3087 if !left_decomp.is_empty() && left_decomp == right_decomp {
3088 if *left_decomp == duration_decomposition() {
3089 LemmaType::anonymous_for_decomposition(duration_decomposition())
3090 } else {
3091 LemmaType::anonymous_for_decomposition(left_decomp.clone())
3092 }
3093 } else if left_type.is_duration_like_quantity()
3094 && right_type.is_duration_like_quantity()
3095 {
3096 LemmaType::anonymous_for_decomposition(duration_decomposition())
3097 } else {
3098 left_type
3099 }
3100 } else {
3101 left_type
3102 }
3103 }
3104 ArithmeticComputation::Multiply | ArithmeticComputation::Divide => {
3105 let combined = combine_decompositions(
3106 l_decomp,
3107 r_decomp,
3108 matches!(op, ArithmeticComputation::Multiply),
3109 );
3110 if combined.is_empty() {
3111 primitive_number().clone()
3112 } else {
3113 LemmaType::anonymous_for_decomposition(combined)
3114 }
3115 }
3116 _ => primitive_number().clone(),
3117 },
3118
3119 (
3120 TypeSpecification::Number { .. },
3121 TypeSpecification::Quantity {
3122 decomposition: r_decomp,
3123 ..
3124 },
3125 ) => {
3126 match op {
3127 ArithmeticComputation::Multiply => right_type,
3128 ArithmeticComputation::Divide => {
3129 if right_type.is_anonymous_quantity() && !r_decomp.is_empty() {
3133 let negated: BaseQuantityVector =
3134 r_decomp.iter().map(|(k, &e)| (k.clone(), -e)).collect();
3135 LemmaType::anonymous_for_decomposition(negated)
3136 } else {
3137 primitive_number().clone()
3138 }
3139 }
3140 _ => primitive_number().clone(),
3141 }
3142 }
3143
3144 (
3145 TypeSpecification::Calendar { .. },
3146 TypeSpecification::Quantity {
3147 decomposition: r_decomp,
3148 ..
3149 },
3150 ) => match op {
3151 ArithmeticComputation::Multiply | ArithmeticComputation::Divide => {
3152 let cal_decomp = calendar_decomposition();
3153 let combined = combine_decompositions(
3154 &cal_decomp,
3155 r_decomp,
3156 matches!(op, ArithmeticComputation::Multiply),
3157 );
3158 if combined.is_empty() {
3159 primitive_number().clone()
3160 } else {
3161 LemmaType::anonymous_for_decomposition(combined)
3162 }
3163 }
3164 _ => primitive_number().clone(),
3165 },
3166 (TypeSpecification::Calendar { .. }, TypeSpecification::Number { .. }) => left_type,
3167 (TypeSpecification::Calendar { .. }, TypeSpecification::Ratio { .. }) => left_type,
3168 (TypeSpecification::Calendar { .. }, TypeSpecification::Calendar { .. }) => match op {
3169 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3170 primitive_calendar().clone()
3171 }
3172 _ => primitive_number().clone(),
3173 },
3174
3175 (TypeSpecification::Number { .. }, TypeSpecification::Calendar { .. }) => match op {
3176 ArithmeticComputation::Multiply => right_type,
3177 _ => primitive_number().clone(),
3178 },
3179
3180 (TypeSpecification::Number { .. }, TypeSpecification::Ratio { .. }) => {
3181 primitive_number().clone()
3182 }
3183 (TypeSpecification::Number { .. }, TypeSpecification::Number { .. }) => {
3184 primitive_number().clone()
3185 }
3186
3187 (TypeSpecification::Ratio { .. }, TypeSpecification::Ratio { .. }) => left_type,
3188 (TypeSpecification::DateRange { .. }, TypeSpecification::DateRange { .. }) => match op {
3189 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3190 range_span_type(&left_type)
3191 }
3192 _ => LemmaType::undetermined_type(),
3193 },
3194 (TypeSpecification::NumberRange { .. }, TypeSpecification::NumberRange { .. }) => {
3195 match op {
3196 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3197 range_span_type(&left_type)
3198 }
3199 _ => LemmaType::undetermined_type(),
3200 }
3201 }
3202 (TypeSpecification::QuantityRange { .. }, TypeSpecification::QuantityRange { .. }) => {
3203 match op {
3204 ArithmeticComputation::Add | ArithmeticComputation::Subtract
3205 if range_matches_range_quantity(&left_type, &right_type) =>
3206 {
3207 range_span_type(&left_type)
3208 }
3209 _ => LemmaType::undetermined_type(),
3210 }
3211 }
3212 (TypeSpecification::RatioRange { .. }, TypeSpecification::RatioRange { .. }) => match op {
3213 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3214 range_span_type(&left_type)
3215 }
3216 _ => LemmaType::undetermined_type(),
3217 },
3218 (TypeSpecification::CalendarRange { .. }, TypeSpecification::CalendarRange { .. }) => {
3219 match op {
3220 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3221 range_span_type(&left_type)
3222 }
3223 _ => LemmaType::undetermined_type(),
3224 }
3225 }
3226 (TypeSpecification::DateRange { .. }, TypeSpecification::CalendarRange { .. })
3227 | (TypeSpecification::CalendarRange { .. }, TypeSpecification::DateRange { .. })
3228 | (TypeSpecification::Date { .. }, TypeSpecification::CalendarRange { .. })
3229 | (TypeSpecification::CalendarRange { .. }, TypeSpecification::Date { .. }) => {
3230 LemmaType::undetermined_type()
3231 }
3232 (TypeSpecification::DateRange { .. }, TypeSpecification::Calendar { .. }) => match op {
3233 ArithmeticComputation::Add | ArithmeticComputation::Subtract => left_type,
3234 _ => LemmaType::undetermined_type(),
3235 },
3236 (TypeSpecification::Calendar { .. }, TypeSpecification::DateRange { .. }) => match op {
3237 ArithmeticComputation::Add | ArithmeticComputation::Subtract => right_type,
3238 _ => LemmaType::undetermined_type(),
3239 },
3240 (TypeSpecification::CalendarRange { .. }, TypeSpecification::Calendar { .. }) => match op {
3241 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3242 range_quantity_type_for_operand(&left_type, &right_type)
3243 }
3244 _ => LemmaType::undetermined_type(),
3245 },
3246 (TypeSpecification::Calendar { .. }, TypeSpecification::CalendarRange { .. }) => match op {
3247 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3248 range_quantity_type_for_operand(&right_type, &left_type)
3249 }
3250 _ => LemmaType::undetermined_type(),
3251 },
3252 (TypeSpecification::NumberRange { .. }, TypeSpecification::Number { .. })
3253 | (TypeSpecification::RatioRange { .. }, TypeSpecification::Ratio { .. }) => match op {
3254 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3255 range_quantity_type_for_operand(&left_type, &right_type)
3256 }
3257 _ => LemmaType::undetermined_type(),
3258 },
3259 (TypeSpecification::QuantityRange { .. }, TypeSpecification::Quantity { .. }) => match op {
3260 ArithmeticComputation::Add | ArithmeticComputation::Subtract
3261 if range_matches_quantity_type(&left_type, &right_type) =>
3262 {
3263 range_quantity_type_for_operand(&left_type, &right_type)
3264 }
3265 _ => LemmaType::undetermined_type(),
3266 },
3267 (TypeSpecification::Number { .. }, TypeSpecification::NumberRange { .. }) => match op {
3268 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3269 range_quantity_type_for_operand(&right_type, &left_type)
3270 }
3271 _ => LemmaType::undetermined_type(),
3272 },
3273 (TypeSpecification::Quantity { .. }, TypeSpecification::QuantityRange { .. }) => match op {
3274 ArithmeticComputation::Add | ArithmeticComputation::Subtract
3275 if range_matches_quantity_type(&right_type, &left_type) =>
3276 {
3277 range_quantity_type_for_operand(&right_type, &left_type)
3278 }
3279 _ => LemmaType::undetermined_type(),
3280 },
3281 (TypeSpecification::Ratio { .. }, TypeSpecification::RatioRange { .. }) => match op {
3282 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3283 range_quantity_type_for_operand(&right_type, &left_type)
3284 }
3285 _ => LemmaType::undetermined_type(),
3286 },
3287 (TypeSpecification::DateRange { .. }, TypeSpecification::Quantity { .. })
3288 if right_type.is_duration_like_quantity() =>
3289 {
3290 match op {
3291 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3292 range_quantity_type_for_operand(&left_type, &right_type)
3293 }
3294 _ => LemmaType::undetermined_type(),
3295 }
3296 }
3297 (TypeSpecification::Quantity { .. }, TypeSpecification::DateRange { .. })
3298 if left_type.is_duration_like_quantity() =>
3299 {
3300 match op {
3301 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
3302 range_quantity_type_for_operand(&right_type, &left_type)
3303 }
3304 _ => LemmaType::undetermined_type(),
3305 }
3306 }
3307 (TypeSpecification::DateRange { .. }, TypeSpecification::Number { .. }) => match op {
3308 ArithmeticComputation::Multiply => range_span_type(&left_type),
3309 _ => LemmaType::undetermined_type(),
3310 },
3311 (TypeSpecification::Number { .. }, TypeSpecification::DateRange { .. }) => match op {
3312 ArithmeticComputation::Multiply => range_span_type(&right_type),
3313 _ => LemmaType::undetermined_type(),
3314 },
3315 (
3316 TypeSpecification::Quantity { decomposition, .. },
3317 TypeSpecification::DateRange { .. },
3318 ) => match (op, date_range_projection_axis(&left_type)) {
3319 (ArithmeticComputation::Multiply, Ok(DateRangeProjectionAxis::Duration)) => {
3320 let combined =
3321 combine_decompositions(decomposition, &duration_decomposition(), true);
3322 if combined.is_empty() {
3323 primitive_number().clone()
3324 } else {
3325 LemmaType::anonymous_for_decomposition(combined)
3326 }
3327 }
3328 (ArithmeticComputation::Multiply, Ok(DateRangeProjectionAxis::Calendar)) => {
3329 let combined =
3330 combine_decompositions(decomposition, &calendar_decomposition(), true);
3331 if combined.is_empty() {
3332 primitive_number().clone()
3333 } else {
3334 LemmaType::anonymous_for_decomposition(combined)
3335 }
3336 }
3337 _ => LemmaType::undetermined_type(),
3338 },
3339 (TypeSpecification::DateRange { .. }, TypeSpecification::Quantity { .. }) => {
3340 compute_arithmetic_result_type_recursive(right_type, op, left_type, true)
3341 }
3342
3343 _ => {
3344 if swapped {
3345 LemmaType::undetermined_type()
3346 } else {
3347 compute_arithmetic_result_type_recursive(right_type, op, left_type, true)
3348 }
3349 }
3350 }
3351}
3352
3353fn infer_range_type_from_endpoint_types(
3354 left_type: &LemmaType,
3355 right_type: &LemmaType,
3356) -> LemmaType {
3357 match (&left_type.specifications, &right_type.specifications) {
3358 (TypeSpecification::Date { .. }, TypeSpecification::Date { .. }) => {
3359 primitive_date_range().clone()
3360 }
3361 (TypeSpecification::Number { .. }, TypeSpecification::Number { .. }) => {
3362 primitive_number_range().clone()
3363 }
3364 (
3365 TypeSpecification::Quantity {
3366 units,
3367 decomposition,
3368 canonical_unit,
3369 ..
3370 },
3371 TypeSpecification::Quantity { .. },
3372 ) if left_type.same_quantity_family(right_type) => {
3373 let mut spec = TypeSpecification::quantity_range();
3374 if let TypeSpecification::QuantityRange {
3375 units: range_units,
3376 decomposition: range_decomposition,
3377 canonical_unit: range_canonical_unit,
3378 ..
3379 } = &mut spec
3380 {
3381 *range_units = units.clone();
3382 *range_decomposition = decomposition.clone();
3383 *range_canonical_unit = canonical_unit.clone();
3384 }
3385 LemmaType::primitive(spec)
3386 }
3387 (TypeSpecification::Ratio { units, .. }, TypeSpecification::Ratio { .. }) => {
3388 let mut spec = TypeSpecification::ratio_range();
3389 if let TypeSpecification::RatioRange {
3390 units: range_units, ..
3391 } = &mut spec
3392 {
3393 *range_units = units.clone();
3394 }
3395 LemmaType::primitive(spec)
3396 }
3397 (TypeSpecification::Calendar { .. }, TypeSpecification::Calendar { .. }) => {
3398 primitive_calendar_range().clone()
3399 }
3400 _ => LemmaType::undetermined_type(),
3401 }
3402}
3403
3404fn range_span_type(range_type: &LemmaType) -> LemmaType {
3405 match &range_type.specifications {
3406 TypeSpecification::DateRange { .. } => {
3407 LemmaType::anonymous_for_decomposition(duration_decomposition())
3408 }
3409 TypeSpecification::NumberRange { .. } => primitive_number().clone(),
3410 TypeSpecification::QuantityRange {
3411 units,
3412 canonical_unit,
3413 ..
3414 } => LemmaType::primitive(TypeSpecification::Quantity {
3415 minimum: None,
3416 maximum: None,
3417 decimals: None,
3418 units: units.clone(),
3419 traits: Vec::new(),
3420 decomposition: BaseQuantityVector::new(),
3423 canonical_unit: canonical_unit.clone(),
3424 help: String::new(),
3425 }),
3426 TypeSpecification::RatioRange { units, .. } => {
3427 LemmaType::primitive(TypeSpecification::Ratio {
3428 minimum: None,
3429 maximum: None,
3430 decimals: None,
3431 units: units.clone(),
3432 help: String::new(),
3433 })
3434 }
3435 TypeSpecification::CalendarRange { .. } => primitive_calendar().clone(),
3436 _ => LemmaType::undetermined_type(),
3437 }
3438}
3439
3440fn range_quantity_type_for_operand(range_type: &LemmaType, other_type: &LemmaType) -> LemmaType {
3441 let _ = other_type;
3442 if range_type.is_range() {
3443 range_type.clone()
3444 } else {
3445 range_span_type(range_type)
3446 }
3447}
3448
3449fn range_matches_quantity_type(range_type: &LemmaType, measure_type: &LemmaType) -> bool {
3450 match &range_type.specifications {
3451 TypeSpecification::DateRange { .. } => {
3452 measure_type.is_duration_like() || measure_type.is_calendar()
3453 }
3454 TypeSpecification::NumberRange { .. } => measure_type.is_number(),
3455 TypeSpecification::QuantityRange { .. } => {
3456 measure_type.is_quantity() && quantity_range_matches_quantity(range_type, measure_type)
3457 }
3458 TypeSpecification::RatioRange { .. } => measure_type.is_ratio(),
3459 TypeSpecification::CalendarRange { .. } => measure_type.is_calendar(),
3460 _ => false,
3461 }
3462}
3463
3464fn range_matches_range_quantity(left_range: &LemmaType, right_range: &LemmaType) -> bool {
3465 let right_measure_type = range_span_type(right_range);
3466 !right_measure_type.is_undetermined()
3467 && range_matches_quantity_type(left_range, &right_measure_type)
3468}
3469
3470#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3471enum DateRangeProjectionAxis {
3472 Duration,
3473 Calendar,
3474}
3475
3476fn date_range_projection_axis(
3477 quantity_type: &LemmaType,
3478) -> Result<DateRangeProjectionAxis, String> {
3479 let quantity_decomposition = match &quantity_type.specifications {
3480 TypeSpecification::Quantity { decomposition, .. } => decomposition,
3481 _ => {
3482 return Err(format!(
3483 "Cannot project date range through non-quantity type {}.",
3484 quantity_type.name()
3485 ));
3486 }
3487 };
3488
3489 let has_duration_axis = quantity_decomposition
3490 .get(semantics::DURATION_DIMENSION)
3491 .is_some_and(|exponent| *exponent != 0);
3492 let has_calendar_axis = quantity_decomposition
3493 .get(semantics::CALENDAR_DIMENSION)
3494 .is_some_and(|exponent| *exponent != 0);
3495
3496 match (has_duration_axis, has_calendar_axis) {
3497 (true, false) => Ok(DateRangeProjectionAxis::Duration),
3498 (false, true) => Ok(DateRangeProjectionAxis::Calendar),
3499 (false, false) => Err(format!(
3500 "Cannot multiply {} by a date range because {} has no duration or calendar dimension.",
3501 quantity_type.name(),
3502 quantity_type.name()
3503 )),
3504 (true, true) => Err(format!(
3505 "Cannot multiply {} by a date range because {} has both duration and calendar dimensions.",
3506 quantity_type.name(),
3507 quantity_type.name()
3508 )),
3509 }
3510}
3511
3512fn quantity_range_matches_quantity(range_type: &LemmaType, quantity_type: &LemmaType) -> bool {
3513 match (&range_type.specifications, &quantity_type.specifications) {
3514 (
3515 TypeSpecification::QuantityRange {
3516 units: range_units,
3517 decomposition: range_decomposition,
3518 canonical_unit: range_canonical_unit,
3519 ..
3520 },
3521 TypeSpecification::Quantity {
3522 units: quantity_units,
3523 decomposition: quantity_decomposition,
3524 canonical_unit: quantity_canonical_unit,
3525 ..
3526 },
3527 ) => {
3528 if range_units.0.is_empty()
3529 && range_decomposition.is_empty()
3530 && range_canonical_unit.is_empty()
3531 {
3532 true
3533 } else if quantity_decomposition.is_empty() {
3534 range_units == quantity_units
3535 && range_canonical_unit.eq_ignore_ascii_case(quantity_canonical_unit)
3536 } else {
3537 range_units == quantity_units
3538 && range_decomposition == quantity_decomposition
3539 && range_canonical_unit.eq_ignore_ascii_case(quantity_canonical_unit)
3540 }
3541 }
3542 _ => false,
3543 }
3544}
3545
3546fn infer_expression_type(
3553 expression: &Expression,
3554 graph: &Graph,
3555 computed_rule_types: &HashMap<RulePath, LemmaType>,
3556 resolved_types: &ResolvedTypesMap,
3557 spec_arc: &Arc<LemmaSpec>,
3558) -> LemmaType {
3559 match &expression.kind {
3560 ExpressionKind::Literal(literal_value) => literal_value.as_ref().get_type().clone(),
3561
3562 ExpressionKind::DataPath(data_path) => {
3563 infer_data_type(data_path, graph, computed_rule_types)
3564 }
3565
3566 ExpressionKind::RulePath(rule_path) => computed_rule_types
3567 .get(rule_path)
3568 .cloned()
3569 .unwrap_or_else(LemmaType::undetermined_type),
3570
3571 ExpressionKind::LogicalAnd(left, right) => {
3572 let left_type =
3573 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_arc);
3574 let right_type =
3575 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_arc);
3576 if left_type.vetoed() || right_type.vetoed() {
3577 return LemmaType::veto_type();
3578 }
3579 if left_type.is_undetermined() || right_type.is_undetermined() {
3580 return LemmaType::undetermined_type();
3581 }
3582 if !left_type.is_boolean() {
3583 return LemmaType::undetermined_type();
3584 }
3585 if right_type.is_boolean() {
3586 primitive_boolean().clone()
3587 } else {
3588 right_type
3589 }
3590 }
3591
3592 ExpressionKind::LogicalOr(left, right) => {
3593 let left_type =
3594 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_arc);
3595 let right_type =
3596 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_arc);
3597 if left_type.vetoed() || right_type.vetoed() {
3598 return LemmaType::veto_type();
3599 }
3600 if left_type.is_undetermined() || right_type.is_undetermined() {
3601 return LemmaType::undetermined_type();
3602 }
3603 if left_type.is_boolean() && right_type.is_boolean() {
3604 return primitive_boolean().clone();
3605 }
3606 if left_type == right_type {
3607 return left_type;
3608 }
3609 LemmaType::undetermined_type()
3610 }
3611
3612 ExpressionKind::LogicalNegation(operand, _) => {
3613 let operand_type = infer_expression_type(
3614 operand,
3615 graph,
3616 computed_rule_types,
3617 resolved_types,
3618 spec_arc,
3619 );
3620 if operand_type.vetoed() {
3621 return LemmaType::veto_type();
3622 }
3623 if operand_type.is_undetermined() {
3624 return LemmaType::undetermined_type();
3625 }
3626 primitive_boolean().clone()
3627 }
3628
3629 ExpressionKind::Comparison(left, _op, right) => {
3630 let left_type =
3631 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_arc);
3632 let right_type =
3633 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_arc);
3634 if left_type.vetoed() || right_type.vetoed() {
3635 return LemmaType::veto_type();
3636 }
3637 if left_type.is_undetermined() || right_type.is_undetermined() {
3638 return LemmaType::undetermined_type();
3639 }
3640 primitive_boolean().clone()
3641 }
3642
3643 ExpressionKind::Arithmetic(left, operator, right) => {
3644 let left_type =
3645 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_arc);
3646 let right_type =
3647 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_arc);
3648 let mut result =
3649 compute_arithmetic_result_type(left_type.clone(), operator, right_type.clone());
3650 if *operator == ArithmeticComputation::Subtract
3651 && left_type.is_time()
3652 && right_type.is_time()
3653 && result.is_anonymous_quantity()
3654 && *result.quantity_type_decomposition() == duration_decomposition()
3655 {
3656 if let Some(duration_type) = find_duration_type_in_spec(resolved_types, spec_arc) {
3657 result = duration_type;
3658 }
3659 }
3660 result
3661 }
3662
3663 ExpressionKind::UnitConversion(source_expression, target) => {
3664 let source_type = infer_expression_type(
3665 source_expression,
3666 graph,
3667 computed_rule_types,
3668 resolved_types,
3669 spec_arc,
3670 );
3671 if source_type.vetoed() {
3672 return LemmaType::veto_type();
3673 }
3674 if source_type.is_undetermined() {
3675 return LemmaType::undetermined_type();
3676 }
3677 if source_type.is_range() {
3678 let span_type = range_span_type(&source_type);
3679 return match target {
3680 SemanticConversionTarget::Number => primitive_number().clone(),
3681 SemanticConversionTarget::Calendar(_) => primitive_calendar().clone(),
3682 SemanticConversionTarget::QuantityUnit(unit_name) => {
3683 find_types_by_spec(resolved_types, spec_arc)
3684 .and_then(|dt| dt.unit_index.get(unit_name))
3685 .cloned()
3686 .unwrap_or(span_type)
3687 }
3688 SemanticConversionTarget::RatioUnit(unit_name) => {
3689 find_types_by_spec(resolved_types, spec_arc)
3690 .and_then(|dt| dt.unit_index.get(unit_name))
3691 .cloned()
3692 .unwrap_or(span_type)
3693 }
3694 };
3695 }
3696 match target {
3697 SemanticConversionTarget::Number => primitive_number().clone(),
3698 SemanticConversionTarget::Calendar(_) => primitive_calendar().clone(),
3699 SemanticConversionTarget::QuantityUnit(unit_name) => {
3700 if source_type.is_number()
3701 || source_type.is_duration_like()
3702 || source_type.is_date_range()
3703 || source_type.is_anonymous_quantity()
3704 {
3705 find_types_by_spec(resolved_types, spec_arc)
3706 .and_then(|dt| dt.unit_index.get(unit_name))
3707 .cloned()
3708 .unwrap_or_else(LemmaType::undetermined_type)
3709 } else {
3710 source_type
3711 }
3712 }
3713 SemanticConversionTarget::RatioUnit(unit_name) => {
3714 if source_type.is_number() {
3715 find_types_by_spec(resolved_types, spec_arc)
3716 .and_then(|dt| dt.unit_index.get(unit_name))
3717 .cloned()
3718 .unwrap_or_else(LemmaType::undetermined_type)
3719 } else {
3720 source_type
3721 }
3722 }
3723 }
3724 }
3725
3726 ExpressionKind::MathematicalComputation(_, operand) => {
3727 let operand_type = infer_expression_type(
3728 operand,
3729 graph,
3730 computed_rule_types,
3731 resolved_types,
3732 spec_arc,
3733 );
3734 if operand_type.vetoed() {
3735 return LemmaType::veto_type();
3736 }
3737 if operand_type.is_undetermined() {
3738 return LemmaType::undetermined_type();
3739 }
3740 primitive_number().clone()
3741 }
3742
3743 ExpressionKind::Veto(_) => LemmaType::veto_type(),
3744
3745 ExpressionKind::ResultIsVeto(operand) => {
3746 let _ = infer_expression_type(
3747 operand,
3748 graph,
3749 computed_rule_types,
3750 resolved_types,
3751 spec_arc,
3752 );
3753 primitive_boolean().clone()
3754 }
3755
3756 ExpressionKind::Now => primitive_date().clone(),
3757
3758 ExpressionKind::DateRelative(..)
3759 | ExpressionKind::DateCalendar(..)
3760 | ExpressionKind::RangeContainment(..) => primitive_boolean().clone(),
3761
3762 ExpressionKind::RangeLiteral(left, right) => {
3763 let left_type =
3764 infer_expression_type(left, graph, computed_rule_types, resolved_types, spec_arc);
3765 let right_type =
3766 infer_expression_type(right, graph, computed_rule_types, resolved_types, spec_arc);
3767 if left_type.vetoed() || right_type.vetoed() {
3768 return LemmaType::veto_type();
3769 }
3770 if left_type.is_undetermined() || right_type.is_undetermined() {
3771 return LemmaType::undetermined_type();
3772 }
3773 infer_range_type_from_endpoint_types(&left_type, &right_type)
3774 }
3775
3776 ExpressionKind::PastFutureRange(..) => primitive_date_range().clone(),
3777 }
3778}
3779
3780fn infer_data_type(
3789 data_path: &DataPath,
3790 graph: &Graph,
3791 computed_rule_types: &HashMap<RulePath, LemmaType>,
3792) -> LemmaType {
3793 let entry = match graph.data().get(data_path) {
3794 Some(e) => e,
3795 None => return LemmaType::undetermined_type(),
3796 };
3797 match entry {
3798 DataDefinition::Value { value, .. } => value.lemma_type.clone(),
3799 DataDefinition::TypeDeclaration { resolved_type, .. } => resolved_type.clone(),
3800 DataDefinition::Reference {
3801 target: ReferenceTarget::Rule(target_rule),
3802 resolved_type,
3803 ..
3804 } => {
3805 if !resolved_type.is_undetermined() {
3806 resolved_type.clone()
3807 } else {
3808 computed_rule_types
3809 .get(target_rule)
3810 .cloned()
3811 .unwrap_or_else(LemmaType::undetermined_type)
3812 }
3813 }
3814 DataDefinition::Reference { resolved_type, .. } => resolved_type.clone(),
3815 DataDefinition::Import { .. } => LemmaType::undetermined_type(),
3816 }
3817}
3818
3819fn collect_rule_reference_dependencies(
3826 expression: &Expression,
3827 reference_to_rule: &HashMap<DataPath, RulePath>,
3828 out: &mut BTreeSet<RulePath>,
3829) {
3830 let mut paths: HashSet<DataPath> = HashSet::new();
3831 expression.kind.collect_data_paths(&mut paths);
3832 for path in paths {
3833 if let Some(target_rule) = reference_to_rule.get(&path) {
3834 out.insert(target_rule.clone());
3835 }
3836 }
3837}
3838
3839fn engine_error_at_graph(graph: &Graph, source: &Source, message: impl Into<String>) -> Error {
3844 Error::validation_with_context(
3845 message.into(),
3846 Some(source.clone()),
3847 None::<String>,
3848 Some(Arc::clone(&graph.main_spec)),
3849 None,
3850 )
3851}
3852
3853fn check_logical_operands(
3854 graph: &Graph,
3855 left_type: &LemmaType,
3856 right_type: &LemmaType,
3857 source: &Source,
3858) -> Result<(), Vec<Error>> {
3859 if left_type.vetoed() || right_type.vetoed() {
3860 return Ok(());
3861 }
3862 let mut errors = Vec::new();
3863 if !left_type.is_boolean() {
3864 errors.push(engine_error_at_graph(
3865 graph,
3866 source,
3867 format!(
3868 "Logical operation requires boolean operands, got {:?} for left operand",
3869 left_type
3870 ),
3871 ));
3872 }
3873 if !right_type.is_boolean() {
3874 errors.push(engine_error_at_graph(
3875 graph,
3876 source,
3877 format!(
3878 "Logical operation requires boolean operands, got {:?} for right operand",
3879 right_type
3880 ),
3881 ));
3882 }
3883 if errors.is_empty() {
3884 Ok(())
3885 } else {
3886 Err(errors)
3887 }
3888}
3889
3890fn check_logical_or_operands(
3891 graph: &Graph,
3892 left_type: &LemmaType,
3893 right_type: &LemmaType,
3894 source: &Source,
3895) -> Result<(), Vec<Error>> {
3896 if left_type.vetoed() || right_type.vetoed() {
3897 return Ok(());
3898 }
3899 if left_type.is_undetermined() || right_type.is_undetermined() {
3900 return Ok(());
3901 }
3902 if left_type.is_boolean() && right_type.is_boolean() {
3903 return check_logical_operands(graph, left_type, right_type, source);
3904 }
3905 if left_type == right_type {
3906 return Ok(());
3907 }
3908 Err(vec![engine_error_at_graph(
3909 graph,
3910 source,
3911 format!(
3912 "Logical OR requires matching types (unless-chain / De Morgan), got {:?} and {:?}",
3913 left_type, right_type
3914 ),
3915 )])
3916}
3917
3918fn check_logical_and_operands(
3919 graph: &Graph,
3920 left_type: &LemmaType,
3921 right_type: &LemmaType,
3922 source: &Source,
3923) -> Result<(), Vec<Error>> {
3924 if left_type.vetoed() || right_type.vetoed() {
3925 return Ok(());
3926 }
3927 if !left_type.is_boolean() {
3928 return Err(vec![engine_error_at_graph(
3929 graph,
3930 source,
3931 format!(
3932 "Logical AND requires boolean left operand, got {:?}",
3933 left_type
3934 ),
3935 )]);
3936 }
3937 if right_type.is_boolean() {
3938 return Ok(());
3939 }
3940 Ok(())
3941}
3942
3943fn check_logical_operand(
3944 graph: &Graph,
3945 operand_type: &LemmaType,
3946 source: &Source,
3947) -> Result<(), Vec<Error>> {
3948 if operand_type.vetoed() {
3949 return Ok(());
3950 }
3951 if !operand_type.is_boolean() {
3952 Err(vec![engine_error_at_graph(
3953 graph,
3954 source,
3955 format!(
3956 "Logical negation requires boolean operand, got {:?}",
3957 operand_type
3958 ),
3959 )])
3960 } else {
3961 Ok(())
3962 }
3963}
3964
3965fn check_comparison_types(
3966 graph: &Graph,
3967 left_type: &LemmaType,
3968 op: &ComparisonComputation,
3969 right_type: &LemmaType,
3970 source: &Source,
3971) -> Result<(), Vec<Error>> {
3972 if left_type.vetoed() || right_type.vetoed() {
3973 return Ok(());
3974 }
3975 let is_equality_only = matches!(op, ComparisonComputation::Is | ComparisonComputation::IsNot);
3976
3977 if left_type.is_range() {
3978 if range_matches_quantity_type(left_type, right_type) {
3979 return Ok(());
3980 }
3981 return Err(vec![engine_error_at_graph(
3982 graph,
3983 source,
3984 format!("Cannot compare {:?} with {:?}", left_type, right_type),
3985 )]);
3986 }
3987
3988 if left_type.is_boolean() && right_type.is_boolean() {
3989 if !is_equality_only {
3990 return Err(vec![engine_error_at_graph(
3991 graph,
3992 source,
3993 format!("Can only use 'is' and 'is not' with booleans (got {})", op),
3994 )]);
3995 }
3996 return Ok(());
3997 }
3998
3999 if left_type.is_text() && right_type.is_text() {
4000 if !is_equality_only {
4001 return Err(vec![engine_error_at_graph(
4002 graph,
4003 source,
4004 format!("Can only use 'is' and 'is not' with text (got {})", op),
4005 )]);
4006 }
4007 return Ok(());
4008 }
4009
4010 if left_type.is_number() && right_type.is_number() {
4011 return Ok(());
4012 }
4013
4014 if left_type.is_ratio() && right_type.is_ratio() {
4015 return Ok(());
4016 }
4017
4018 if left_type.is_date() && right_type.is_date() {
4019 return Ok(());
4020 }
4021
4022 if left_type.is_time() && right_type.is_time() {
4023 return Ok(());
4024 }
4025
4026 if left_type.is_quantity() && right_type.is_quantity() {
4027 if !left_type.same_quantity_family(right_type)
4028 && !left_type.compatible_with_anonymous_quantity(right_type)
4029 {
4030 return Err(vec![engine_error_at_graph(
4031 graph,
4032 source,
4033 format!(
4034 "Cannot compare unrelated quantity types: {} and {}",
4035 left_type.name(),
4036 right_type.name()
4037 ),
4038 )]);
4039 }
4040 return Ok(());
4041 }
4042
4043 if left_type.is_duration_like() && right_type.is_duration_like() {
4044 return Ok(());
4045 }
4046 if left_type.is_calendar() && right_type.is_calendar() {
4047 return Ok(());
4048 }
4049 if left_type.is_calendar() && right_type.is_number() {
4050 return Ok(());
4051 }
4052 if left_type.is_number() && right_type.is_calendar() {
4053 return Ok(());
4054 }
4055
4056 Err(vec![engine_error_at_graph(
4057 graph,
4058 source,
4059 format!("Cannot compare {:?} with {:?}", left_type, right_type),
4060 )])
4061}
4062
4063fn arithmetic_literal_zero_divisor_planning_errors(
4065 graph: &Graph,
4066 right: &Expression,
4067 operator: &ArithmeticComputation,
4068 source: &Source,
4069) -> Result<(), Vec<Error>> {
4070 if !matches!(
4071 operator,
4072 ArithmeticComputation::Divide | ArithmeticComputation::Modulo
4073 ) {
4074 return Ok(());
4075 }
4076
4077 if let ExpressionKind::Literal(literal) = &right.kind {
4078 if let ValueKind::Number(number) = &literal.value {
4079 if crate::computation::rational::rational_is_zero(number) {
4080 return Err(vec![engine_error_at_graph(
4081 graph,
4082 source,
4083 format!("Cannot apply '{}' with a zero divisor literal.", operator),
4084 )]);
4085 }
4086 }
4087 }
4088
4089 Ok(())
4090}
4091
4092fn arithmetic_power_exponent_planning_errors(
4093 graph: &Graph,
4094 _left: &Expression,
4095 right: &Expression,
4096 left_type: &LemmaType,
4097 _right_type: &LemmaType,
4098 operator: &ArithmeticComputation,
4099 source: &Source,
4100) -> Result<(), Vec<Error>> {
4101 if *operator != ArithmeticComputation::Power {
4102 return Ok(());
4103 }
4104 if left_type.is_quantity() || left_type.is_duration_like() {
4107 let is_integer_literal = if let ExpressionKind::Literal(lit) = &right.kind {
4108 if let crate::planning::semantics::ValueKind::Number(n) = &lit.value {
4109 *n.denom() == 1
4110 } else {
4111 false
4112 }
4113 } else {
4114 false
4115 };
4116 if !is_integer_literal {
4117 return Err(vec![engine_error_at_graph(
4118 graph,
4119 source,
4120 "Cannot raise a quantity value to a fractional or variable exponent. Use a positive integer literal.".to_string(),
4121 )]);
4122 }
4123 }
4124 Ok(())
4125}
4126
4127fn arithmetic_plan_time_exactness_planning_errors(
4130 graph: &Graph,
4131 left: &Expression,
4132 right: &Expression,
4133 left_type: &LemmaType,
4134 right_type: &LemmaType,
4135 operator: &ArithmeticComputation,
4136 source: &Source,
4137) -> Result<(), Vec<Error>> {
4138 if left_type.vetoed() || right_type.vetoed() {
4139 return Ok(());
4140 }
4141 if left_type.is_undetermined() || right_type.is_undetermined() {
4142 return Ok(());
4143 }
4144
4145 let mut errors = Vec::new();
4146 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
4147 if let Err(mut errs) = result {
4148 errors.append(&mut errs);
4149 }
4150 };
4151
4152 collect(
4153 arithmetic_literal_zero_divisor_planning_errors(graph, right, operator, source),
4154 &mut errors,
4155 );
4156 collect(
4157 arithmetic_power_exponent_planning_errors(
4158 graph, left, right, left_type, right_type, operator, source,
4159 ),
4160 &mut errors,
4161 );
4162
4163 if errors.is_empty() {
4164 Ok(())
4165 } else {
4166 Err(errors)
4167 }
4168}
4169
4170fn check_arithmetic_types(
4171 graph: &Graph,
4172 left_type: &LemmaType,
4173 right_type: &LemmaType,
4174 operator: &ArithmeticComputation,
4175 source: &Source,
4176) -> Result<(), Vec<Error>> {
4177 if left_type.vetoed() || right_type.vetoed() {
4178 return Ok(());
4179 }
4180
4181 if left_type.is_date() && right_type.is_date() && *operator == ArithmeticComputation::Subtract {
4182 return Err(vec![engine_error_at_graph(
4183 graph,
4184 source,
4185 "Cannot subtract dates. Use dateA...dateB to create a date range.".to_string(),
4186 )]);
4187 }
4188
4189 if left_type.is_range() || right_type.is_range() {
4190 let range_measure_allowed = matches!(
4191 operator,
4192 ArithmeticComputation::Add | ArithmeticComputation::Subtract
4193 ) && ((left_type.is_range()
4194 && right_type.is_range()
4195 && range_matches_range_quantity(left_type, right_type))
4196 || (left_type.is_range()
4197 && !right_type.is_range()
4198 && range_matches_quantity_type(left_type, right_type))
4199 || (right_type.is_range()
4200 && !left_type.is_range()
4201 && range_matches_quantity_type(right_type, left_type)));
4202 let range_scalar_multiply_allowed = *operator == ArithmeticComputation::Multiply
4203 && ((left_type.is_range() && right_type.is_number())
4204 || (right_type.is_range() && left_type.is_number()));
4205
4206 let quantity_date_range_allowed = if *operator == ArithmeticComputation::Multiply {
4207 if left_type.is_quantity() && right_type.is_date_range() {
4208 match date_range_projection_axis(left_type) {
4209 Ok(_) => true,
4210 Err(message) => {
4211 return Err(vec![engine_error_at_graph(graph, source, message)]);
4212 }
4213 }
4214 } else if left_type.is_date_range() && right_type.is_quantity() {
4215 match date_range_projection_axis(right_type) {
4216 Ok(_) => true,
4217 Err(message) => {
4218 return Err(vec![engine_error_at_graph(graph, source, message)]);
4219 }
4220 }
4221 } else {
4222 false
4223 }
4224 } else {
4225 false
4226 };
4227
4228 if range_measure_allowed || range_scalar_multiply_allowed || quantity_date_range_allowed {
4229 return Ok(());
4230 }
4231
4232 if (left_type.is_date_range()
4233 && right_type.is_quantity()
4234 && !right_type.is_duration_like_quantity())
4235 || (right_type.is_date_range()
4236 && left_type.is_quantity()
4237 && !left_type.is_duration_like_quantity())
4238 {
4239 return Err(vec![engine_error_at_graph(
4240 graph,
4241 source,
4242 format!(
4243 "Cannot apply '{}' to a date range and an unrelated quantity.",
4244 operator
4245 ),
4246 )]);
4247 }
4248
4249 return Err(vec![engine_error_at_graph(
4250 graph,
4251 source,
4252 format!(
4253 "Cannot apply '{}' to {} and {}.",
4254 operator,
4255 left_type.name(),
4256 right_type.name()
4257 ),
4258 )]);
4259 }
4260
4261 if left_type.is_date() || left_type.is_time() || right_type.is_date() || right_type.is_time() {
4263 let left_is_duration_like = left_type.is_duration_like();
4264 let right_is_duration_like = right_type.is_duration_like();
4265 let valid = matches!(
4266 (
4267 left_type.is_date(),
4268 left_type.is_time(),
4269 right_type.is_date(),
4270 right_type.is_time(),
4271 left_is_duration_like,
4272 right_is_duration_like,
4273 left_type.is_calendar(),
4274 right_type.is_calendar(),
4275 operator
4276 ),
4277 (
4278 true,
4279 _,
4280 _,
4281 true,
4282 _,
4283 _,
4284 _,
4285 _,
4286 ArithmeticComputation::Subtract
4287 ) | (
4288 _,
4289 true,
4290 _,
4291 true,
4292 _,
4293 _,
4294 _,
4295 _,
4296 ArithmeticComputation::Subtract
4297 ) | (
4298 true,
4299 _,
4300 _,
4301 _,
4302 _,
4303 true,
4304 _,
4305 _,
4306 ArithmeticComputation::Add | ArithmeticComputation::Subtract
4307 ) | (
4308 true,
4309 _,
4310 _,
4311 _,
4312 _,
4313 _,
4314 _,
4315 true,
4316 ArithmeticComputation::Add | ArithmeticComputation::Subtract
4317 ) | (_, _, true, _, true, _, _, _, ArithmeticComputation::Add)
4318 | (_, _, true, _, _, _, true, _, ArithmeticComputation::Add)
4319 | (
4320 _,
4321 true,
4322 _,
4323 _,
4324 _,
4325 true,
4326 _,
4327 _,
4328 ArithmeticComputation::Add | ArithmeticComputation::Subtract
4329 )
4330 | (_, _, _, true, true, _, _, _, ArithmeticComputation::Add)
4331 );
4332 if !valid {
4333 return Err(vec![engine_error_at_graph(
4334 graph,
4335 source,
4336 format!(
4337 "Cannot apply '{}' to {} and {}.",
4338 operator,
4339 left_type.name(),
4340 right_type.name()
4341 ),
4342 )]);
4343 }
4344 return Ok(());
4345 }
4346
4347 if left_type.is_quantity() && right_type.is_quantity() {
4353 return match operator {
4354 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
4355 if left_type.same_quantity_family(right_type)
4356 || left_type.compatible_with_anonymous_quantity(right_type)
4357 {
4358 Ok(())
4359 } else {
4360 Err(vec![engine_error_at_graph(
4361 graph,
4362 source,
4363 format!(
4364 "Cannot {} unrelated quantity types: {} and {}.",
4365 if matches!(operator, ArithmeticComputation::Add) {
4366 "add"
4367 } else {
4368 "subtract"
4369 },
4370 left_type.name(),
4371 right_type.name()
4372 ),
4373 )])
4374 }
4375 }
4376 ArithmeticComputation::Multiply => {
4377 if left_type.same_quantity_family(right_type) {
4378 Err(vec![engine_error_at_graph(
4379 graph,
4380 source,
4381 format!(
4382 "Cannot multiply two '{}' quantity values of the same type. \
4383 Convert operands first: 'value as number'.",
4384 left_type.name()
4385 ),
4386 )])
4387 } else {
4388 Ok(())
4390 }
4391 }
4392 ArithmeticComputation::Divide => {
4393 Ok(())
4395 }
4396 ArithmeticComputation::Modulo | ArithmeticComputation::Power => {
4397 Err(vec![engine_error_at_graph(
4398 graph,
4399 source,
4400 format!(
4401 "Cannot apply '{}' to two quantity values ({} and {}).",
4402 operator,
4403 left_type.name(),
4404 right_type.name()
4405 ),
4406 )])
4407 }
4408 };
4409 }
4410
4411 if left_type.is_duration_like() && right_type.is_duration_like() {
4413 return match operator {
4414 ArithmeticComputation::Add | ArithmeticComputation::Subtract => Ok(()),
4415 ArithmeticComputation::Divide => Ok(()),
4416 _ => Err(vec![engine_error_at_graph(
4417 graph,
4418 source,
4419 "Cannot multiply two duration values. Convert operands first: 'value as number'."
4420 .to_string(),
4421 )]),
4422 };
4423 }
4424
4425 if left_type.is_calendar() && right_type.is_calendar() {
4426 return match operator {
4427 ArithmeticComputation::Add | ArithmeticComputation::Subtract => Ok(()),
4428 ArithmeticComputation::Divide => Ok(()),
4429 _ => Err(vec![engine_error_at_graph(
4430 graph,
4431 source,
4432 "Cannot multiply two calendar values. Convert operands first: 'value as number'."
4433 .to_string(),
4434 )]),
4435 };
4436 }
4437
4438 if (left_type.is_duration_like() && right_type.is_calendar())
4439 || (left_type.is_calendar() && right_type.is_duration_like())
4440 {
4441 return Err(vec![engine_error_at_graph(
4442 graph,
4443 source,
4444 format!(
4445 "Cannot apply '{}' to {} and {}. Duration and calendar are unrelated types.",
4446 operator,
4447 left_type.name(),
4448 right_type.name()
4449 ),
4450 )]);
4451 }
4452
4453 let left_valid = left_type.is_quantity()
4455 || left_type.is_number()
4456 || left_type.is_duration_like()
4457 || left_type.is_calendar()
4458 || left_type.is_ratio();
4459 let right_valid = right_type.is_quantity()
4460 || right_type.is_number()
4461 || right_type.is_duration_like()
4462 || right_type.is_calendar()
4463 || right_type.is_ratio();
4464
4465 if !left_valid || !right_valid {
4466 return Err(vec![engine_error_at_graph(
4467 graph,
4468 source,
4469 format!(
4470 "Cannot apply '{}' to {} and {}.",
4471 operator,
4472 left_type.name(),
4473 right_type.name()
4474 ),
4475 )]);
4476 }
4477
4478 if left_type.has_same_base_type(right_type) {
4480 return Ok(());
4481 }
4482
4483 let pair = |a: fn(&LemmaType) -> bool, b: fn(&LemmaType) -> bool| {
4484 (a(left_type) && b(right_type)) || (b(left_type) && a(right_type))
4485 };
4486
4487 let allowed = match operator {
4488 ArithmeticComputation::Multiply => {
4489 pair(LemmaType::is_quantity, LemmaType::is_number)
4490 || pair(LemmaType::is_quantity, LemmaType::is_ratio)
4491 || pair(LemmaType::is_quantity, LemmaType::is_duration_like_quantity)
4492 || pair(LemmaType::is_quantity, LemmaType::is_calendar)
4493 || pair(LemmaType::is_duration_like_quantity, LemmaType::is_number)
4494 || pair(LemmaType::is_duration_like_quantity, LemmaType::is_ratio)
4495 || pair(LemmaType::is_calendar, LemmaType::is_number)
4496 || pair(LemmaType::is_calendar, LemmaType::is_ratio)
4497 || pair(LemmaType::is_number, LemmaType::is_ratio)
4498 }
4499 ArithmeticComputation::Divide => {
4500 pair(LemmaType::is_quantity, LemmaType::is_number)
4501 || pair(LemmaType::is_quantity, LemmaType::is_ratio)
4502 || pair(LemmaType::is_quantity, LemmaType::is_duration_like_quantity)
4503 || pair(LemmaType::is_quantity, LemmaType::is_calendar)
4504 || (left_type.is_duration_like() && right_type.is_number())
4505 || (left_type.is_duration_like() && right_type.is_ratio())
4506 || (left_type.is_calendar() && right_type.is_number())
4507 || (left_type.is_calendar() && right_type.is_ratio())
4508 || (left_type.is_number() && right_type.is_duration_like())
4509 || (left_type.is_number() && right_type.is_calendar())
4510 || pair(LemmaType::is_number, LemmaType::is_ratio)
4511 }
4512 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
4513 pair(LemmaType::is_quantity, LemmaType::is_number)
4514 || pair(LemmaType::is_quantity, LemmaType::is_ratio)
4515 || pair(LemmaType::is_duration_like_quantity, LemmaType::is_number)
4516 || pair(LemmaType::is_duration_like_quantity, LemmaType::is_ratio)
4517 || pair(LemmaType::is_calendar, LemmaType::is_number)
4518 || pair(LemmaType::is_calendar, LemmaType::is_ratio)
4519 || pair(LemmaType::is_number, LemmaType::is_ratio)
4520 }
4521 ArithmeticComputation::Power => {
4522 let left_ok = left_type.is_number()
4526 || left_type.is_quantity()
4527 || left_type.is_ratio()
4528 || left_type.is_duration_like();
4529 let right_ok = if left_type.is_quantity() || left_type.is_duration_like() {
4530 right_type.is_number()
4531 } else {
4532 right_type.is_number() || right_type.is_ratio()
4533 };
4534 left_ok && right_ok
4535 }
4536 ArithmeticComputation::Modulo => {
4537 if left_type.is_quantity() && right_type.is_ratio() {
4540 return Err(vec![engine_error_at_graph(
4541 graph,
4542 source,
4543 format!(
4544 "Cannot apply modulo to {} with a ratio. Use a number divisor.",
4545 left_type.name()
4546 ),
4547 )]);
4548 }
4549 right_type.is_number() || right_type.is_ratio()
4550 }
4551 };
4552
4553 if !allowed {
4554 return Err(vec![engine_error_at_graph(
4555 graph,
4556 source,
4557 format!(
4558 "Cannot apply '{}' to {} and {}.",
4559 operator,
4560 left_type.name(),
4561 right_type.name(),
4562 ),
4563 )]);
4564 }
4565
4566 Ok(())
4567}
4568
4569fn check_range_span_unit_conversion(
4570 graph: &Graph,
4571 source_type: &LemmaType,
4572 target: &SemanticConversionTarget,
4573 resolved_types: &ResolvedTypesMap,
4574 source: &Source,
4575 spec_arc: &Arc<LemmaSpec>,
4576) -> Result<(), Vec<Error>> {
4577 match target {
4578 SemanticConversionTarget::Number => {
4579 if source_type.is_number_range() {
4580 Ok(())
4581 } else {
4582 Err(vec![engine_error_at_graph(
4583 graph,
4584 source,
4585 format!("Cannot convert {} to number.", source_type.name()),
4586 )])
4587 }
4588 }
4589 SemanticConversionTarget::QuantityUnit(unit_name) => {
4590 if source_type.is_calendar_range() {
4591 return Err(vec![engine_error_at_graph(
4592 graph,
4593 source,
4594 format!(
4595 "Cannot convert {} to quantity unit '{}'.",
4596 source_type.name(),
4597 unit_name
4598 ),
4599 )]);
4600 }
4601 let target_type = find_types_by_spec(resolved_types, spec_arc)
4602 .and_then(|dt| dt.unit_index.get(unit_name))
4603 .cloned();
4604 let target_type = match target_type {
4605 Some(lemma_type) => lemma_type,
4606 None => {
4607 return Err(vec![engine_error_at_graph(
4608 graph,
4609 source,
4610 format!(
4611 "Unknown unit '{}': no quantity type in spec '{}' owns this unit.",
4612 unit_name, spec_arc.name
4613 ),
4614 )]);
4615 }
4616 };
4617 if !target_type.is_duration_like_quantity() {
4618 return Err(vec![engine_error_at_graph(
4619 graph,
4620 source,
4621 format!(
4622 "Cannot convert {} to quantity unit '{}'.",
4623 source_type.name(),
4624 unit_name
4625 ),
4626 )]);
4627 }
4628 if source_type.is_number_range() {
4629 return Ok(());
4630 }
4631 if source_type.is_quantity_range() {
4632 if let TypeSpecification::QuantityRange {
4633 decomposition,
4634 units,
4635 ..
4636 } = &source_type.specifications
4637 {
4638 if *decomposition == duration_decomposition() {
4639 return Ok(());
4640 }
4641 let all_duration_endpoints = !units.0.is_empty()
4642 && units.0.iter().all(|unit| {
4643 find_types_by_spec(resolved_types, spec_arc)
4644 .and_then(|dt| dt.unit_index.get(&unit.name))
4645 .is_some_and(|owner| owner.is_duration_like_quantity())
4646 });
4647 if all_duration_endpoints {
4648 return Ok(());
4649 }
4650 }
4651 return Err(vec![engine_error_at_graph(
4652 graph,
4653 source,
4654 format!(
4655 "Cannot convert {} to quantity unit '{}'.",
4656 source_type.name(),
4657 unit_name
4658 ),
4659 )]);
4660 }
4661 Err(vec![engine_error_at_graph(
4662 graph,
4663 source,
4664 format!(
4665 "Cannot convert {} to quantity unit '{}'.",
4666 source_type.name(),
4667 unit_name
4668 ),
4669 )])
4670 }
4671 SemanticConversionTarget::RatioUnit(unit_name) => {
4672 if !source_type.is_ratio_range() {
4673 return Err(vec![engine_error_at_graph(
4674 graph,
4675 source,
4676 format!(
4677 "Cannot convert {} to ratio unit '{}'.",
4678 source_type.name(),
4679 unit_name
4680 ),
4681 )]);
4682 }
4683 let valid: Vec<&str> = match &source_type.specifications {
4684 TypeSpecification::RatioRange { units, .. } => {
4685 units.iter().map(|u| u.name.as_str()).collect()
4686 }
4687 _ => unreachable!("BUG: is_ratio_range without RatioRange spec"),
4688 };
4689 if valid
4690 .iter()
4691 .any(|name| name.eq_ignore_ascii_case(unit_name))
4692 {
4693 Ok(())
4694 } else {
4695 Err(vec![engine_error_at_graph(
4696 graph,
4697 source,
4698 format!(
4699 "Unknown unit '{}' for type {}. Valid units: {}",
4700 unit_name,
4701 source_type.name(),
4702 valid.join(", ")
4703 ),
4704 )])
4705 }
4706 }
4707 SemanticConversionTarget::Calendar(_) => Err(vec![engine_error_at_graph(
4708 graph,
4709 source,
4710 format!("Cannot convert {} to calendar.", source_type.name()),
4711 )]),
4712 }
4713}
4714
4715fn check_unit_conversion_types(
4716 graph: &Graph,
4717 source_type: &LemmaType,
4718 target: &SemanticConversionTarget,
4719 resolved_types: &ResolvedTypesMap,
4720 source: &Source,
4721 spec_arc: &Arc<LemmaSpec>,
4722) -> Result<(), Vec<Error>> {
4723 if source_type.vetoed() {
4724 return Ok(());
4725 }
4726 match target {
4727 SemanticConversionTarget::Number => {
4728 if source_type.is_date_range() || source_type.is_number_range() {
4729 return Ok(());
4730 }
4731 if source_type.is_quantity_range()
4732 || source_type.is_ratio_range()
4733 || source_type.is_calendar_range()
4734 {
4735 return check_range_span_unit_conversion(
4736 graph,
4737 source_type,
4738 target,
4739 resolved_types,
4740 source,
4741 spec_arc,
4742 );
4743 }
4744 if source_type.is_anonymous_quantity() {
4749 let decomp = source_type.quantity_type_decomposition();
4750 if !decomp.is_empty() {
4751 return Err(vec![engine_error_at_graph(
4752 graph,
4753 source,
4754 format!(
4755 "Cannot use 'as number' to strip an anonymous intermediate with unresolved \
4756 dimensions {:?}. Cast to a named quantity typedef first (e.g., 'as <unit>'), \
4757 or ensure all dimensions cancel before converting to number.",
4758 decomp
4759 ),
4760 )]);
4761 }
4762 }
4763 if source_type.is_quantity()
4764 || source_type.is_number()
4765 || source_type.is_duration_like()
4766 || source_type.is_calendar()
4767 || source_type.is_ratio()
4768 {
4769 Ok(())
4770 } else {
4771 Err(vec![engine_error_at_graph(
4772 graph,
4773 source,
4774 format!("Cannot convert {} to number.", source_type.name()),
4775 )])
4776 }
4777 }
4778 SemanticConversionTarget::QuantityUnit(unit_name) => {
4779 if source_type.is_date_range() {
4780 let target_type = find_types_by_spec(resolved_types, spec_arc)
4781 .and_then(|dt| dt.unit_index.get(unit_name))
4782 .cloned();
4783
4784 let target_type = match target_type {
4785 Some(lemma_type) => lemma_type,
4786 None => {
4787 return Err(vec![engine_error_at_graph(
4788 graph,
4789 source,
4790 format!(
4791 "Unknown unit '{}': no quantity type in spec '{}' owns this unit.",
4792 unit_name, spec_arc.name
4793 ),
4794 )]);
4795 }
4796 };
4797
4798 if !target_type.is_duration_like_quantity() {
4799 return Err(vec![engine_error_at_graph(
4800 graph,
4801 source,
4802 format!(
4803 "Cannot convert date range to quantity unit '{}'.",
4804 unit_name
4805 ),
4806 )]);
4807 }
4808
4809 return Ok(());
4810 }
4811
4812 if source_type.is_number_range()
4813 || source_type.is_quantity_range()
4814 || source_type.is_calendar_range()
4815 {
4816 return check_range_span_unit_conversion(
4817 graph,
4818 source_type,
4819 target,
4820 resolved_types,
4821 source,
4822 spec_arc,
4823 );
4824 }
4825 if source_type.is_number() {
4827 return if find_types_by_spec(resolved_types, spec_arc)
4828 .and_then(|dt| dt.unit_index.get(unit_name))
4829 .is_some()
4830 {
4831 Ok(())
4832 } else {
4833 Err(vec![engine_error_at_graph(
4834 graph,
4835 source,
4836 format!("Unknown unit '{}' in spec '{}'.", unit_name, spec_arc.name),
4837 )])
4838 };
4839 }
4840
4841 if source_type.is_anonymous_quantity() {
4844 let target_type = find_types_by_spec(resolved_types, spec_arc)
4845 .and_then(|dt| dt.unit_index.get(unit_name))
4846 .cloned();
4847
4848 let target_type = match target_type {
4849 Some(lemma_type) => lemma_type,
4850 None => {
4851 return Err(vec![engine_error_at_graph(
4852 graph,
4853 source,
4854 format!(
4855 "Unknown unit '{}': no quantity type in spec '{}' owns this unit.",
4856 unit_name, spec_arc.name
4857 ),
4858 )]);
4859 }
4860 };
4861
4862 let source_decomp = source_type.quantity_type_decomposition();
4863 let target_quantity_family = target_type
4864 .quantity_family_name()
4865 .map(str::to_string)
4866 .unwrap_or_else(|| target_type.name().to_string());
4867
4868 let target_decomp = match &target_type.specifications {
4869 TypeSpecification::Quantity { decomposition, .. } => decomposition.clone(),
4870 _ => {
4871 return Err(vec![engine_error_at_graph(
4872 graph,
4873 source,
4874 format!("Unit '{}' does not belong to a quantity type.", unit_name),
4875 )]);
4876 }
4877 };
4878
4879 if *source_decomp != target_decomp {
4880 return Err(vec![engine_error_at_graph(
4881 graph,
4882 source,
4883 format!(
4884 "Cannot cast to '{}' (quantity '{}'): source dimensions {:?} do not \
4885 match target dimensions {:?}. The intermediate result has a different \
4886 physical quantity than the target type.",
4887 unit_name, target_quantity_family, source_decomp, target_decomp
4888 ),
4889 )]);
4890 }
4891
4892 return Ok(());
4893 }
4894
4895 match source_type.validate_quantity_result_unit(unit_name) {
4896 Ok(()) => Ok(()),
4897 Err(message) => Err(vec![engine_error_at_graph(graph, source, message)]),
4898 }
4899 }
4900 SemanticConversionTarget::RatioUnit(unit_name) => {
4901 if source_type.is_ratio_range() {
4902 return check_range_span_unit_conversion(
4903 graph,
4904 source_type,
4905 target,
4906 resolved_types,
4907 source,
4908 spec_arc,
4909 );
4910 }
4911 let unit_check: Option<(bool, Vec<&str>)> = match &source_type.specifications {
4912 TypeSpecification::Ratio { units, .. } => {
4913 let valid: Vec<&str> = units.iter().map(|u| u.name.as_str()).collect();
4914 let found = units.iter().any(|u| u.name.eq_ignore_ascii_case(unit_name));
4915 Some((found, valid))
4916 }
4917 _ => None,
4918 };
4919
4920 match unit_check {
4921 Some((true, _)) => Ok(()),
4922 Some((false, valid)) => Err(vec![engine_error_at_graph(
4923 graph,
4924 source,
4925 format!(
4926 "Unknown unit '{}' for type {}. Valid units: {}",
4927 unit_name,
4928 source_type.name(),
4929 valid.join(", ")
4930 ),
4931 )]),
4932 None if source_type.is_number() => {
4933 if find_types_by_spec(resolved_types, spec_arc)
4934 .and_then(|dt| dt.unit_index.get(unit_name))
4935 .is_none()
4936 {
4937 Err(vec![engine_error_at_graph(
4938 graph,
4939 source,
4940 format!("Unknown unit '{}' in spec '{}'.", unit_name, spec_arc.name),
4941 )])
4942 } else {
4943 Ok(())
4944 }
4945 }
4946 None => Err(vec![engine_error_at_graph(
4947 graph,
4948 source,
4949 format!(
4950 "Cannot convert {} to ratio unit '{}'.",
4951 source_type.name(),
4952 unit_name
4953 ),
4954 )]),
4955 }
4956 }
4957 SemanticConversionTarget::Calendar(_) => {
4958 if !source_type.is_calendar()
4959 && !source_type.is_number()
4960 && !source_type.is_date_range()
4961 {
4962 Err(vec![engine_error_at_graph(
4963 graph,
4964 source,
4965 format!("Cannot convert {} to calendar.", source_type.name()),
4966 )])
4967 } else {
4968 Ok(())
4969 }
4970 }
4971 }
4972}
4973
4974fn check_mathematical_operand(
4975 graph: &Graph,
4976 operand_type: &LemmaType,
4977 source: &Source,
4978) -> Result<(), Vec<Error>> {
4979 if operand_type.vetoed() {
4980 return Ok(());
4981 }
4982 if !operand_type.is_number() {
4983 Err(vec![engine_error_at_graph(
4984 graph,
4985 source,
4986 format!(
4987 "Mathematical function requires number operand, got {:?}",
4988 operand_type
4989 ),
4990 )])
4991 } else {
4992 Ok(())
4993 }
4994}
4995
4996fn check_all_rule_references_exist(graph: &Graph) -> Result<(), Vec<Error>> {
4998 let mut errors = Vec::new();
4999 let existing_rules: HashSet<&RulePath> = graph.rules().keys().collect();
5000 for (rule_path, rule_node) in graph.rules() {
5001 for dependency in &rule_node.depends_on_rules {
5002 if !existing_rules.contains(dependency) {
5003 errors.push(engine_error_at_graph(
5004 graph,
5005 &rule_node.source,
5006 format!(
5007 "Rule '{}' references non-existent rule '{}'",
5008 rule_path.rule, dependency.rule
5009 ),
5010 ));
5011 }
5012 }
5013 }
5014 if errors.is_empty() {
5015 Ok(())
5016 } else {
5017 Err(errors)
5018 }
5019}
5020
5021fn check_data_and_rule_name_collisions(graph: &Graph) -> Result<(), Vec<Error>> {
5023 let mut errors = Vec::new();
5024 for rule_path in graph.rules().keys() {
5025 let data_path = DataPath::new(rule_path.segments.clone(), rule_path.rule.clone());
5026 if graph.data().contains_key(&data_path) {
5027 let rule_node = graph.rules().get(rule_path).unwrap_or_else(|| {
5028 unreachable!(
5029 "BUG: rule '{}' missing from graph while validating name collisions",
5030 rule_path.rule
5031 )
5032 });
5033 errors.push(engine_error_at_graph(
5034 graph,
5035 &rule_node.source,
5036 format!(
5037 "Name collision: '{}' is defined as both a data and a rule",
5038 data_path
5039 ),
5040 ));
5041 }
5042 }
5043 if errors.is_empty() {
5044 Ok(())
5045 } else {
5046 Err(errors)
5047 }
5048}
5049
5050fn check_data_reference(
5052 data_path: &DataPath,
5053 graph: &Graph,
5054 data_source: &Source,
5055) -> Result<(), Vec<Error>> {
5056 let entry = match graph.data().get(data_path) {
5057 Some(e) => e,
5058 None => {
5059 return Err(vec![engine_error_at_graph(
5060 graph,
5061 data_source,
5062 format!("Unknown data reference '{}'", data_path),
5063 )]);
5064 }
5065 };
5066 match entry {
5067 DataDefinition::Value { .. }
5068 | DataDefinition::TypeDeclaration { .. }
5069 | DataDefinition::Reference { .. } => Ok(()),
5070 DataDefinition::Import { .. } => Err(vec![engine_error_at_graph(
5071 graph,
5072 entry.source(),
5073 format!(
5074 "Cannot compute type for spec reference data '{}'",
5075 data_path
5076 ),
5077 )]),
5078 }
5079}
5080
5081fn check_expression(
5085 expression: &Expression,
5086 graph: &Graph,
5087 inferred_types: &HashMap<RulePath, LemmaType>,
5088 resolved_types: &ResolvedTypesMap,
5089 spec_arc: &Arc<LemmaSpec>,
5090) -> Result<(), Vec<Error>> {
5091 let mut errors = Vec::new();
5092
5093 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
5094 if let Err(errs) = result {
5095 errors.extend(errs);
5096 }
5097 };
5098
5099 match &expression.kind {
5100 ExpressionKind::Literal(_) => {}
5101
5102 ExpressionKind::DataPath(data_path) => {
5103 let data_source = expression
5104 .source_location
5105 .as_ref()
5106 .expect("BUG: expression missing source in check_expression");
5107 collect(
5108 check_data_reference(data_path, graph, data_source),
5109 &mut errors,
5110 );
5111 }
5112
5113 ExpressionKind::RulePath(_) => {}
5114
5115 ExpressionKind::LogicalAnd(left, right) => {
5116 collect(
5117 check_expression(left, graph, inferred_types, resolved_types, spec_arc),
5118 &mut errors,
5119 );
5120 collect(
5121 check_expression(right, graph, inferred_types, resolved_types, spec_arc),
5122 &mut errors,
5123 );
5124
5125 let left_type =
5126 infer_expression_type(left, graph, inferred_types, resolved_types, spec_arc);
5127 let right_type =
5128 infer_expression_type(right, graph, inferred_types, resolved_types, spec_arc);
5129 let expr_source = expression
5130 .source_location
5131 .as_ref()
5132 .expect("BUG: expression missing source in check_expression");
5133 collect(
5134 check_logical_and_operands(graph, &left_type, &right_type, expr_source),
5135 &mut errors,
5136 );
5137 }
5138
5139 ExpressionKind::LogicalOr(left, right) => {
5140 collect(
5141 check_expression(left, graph, inferred_types, resolved_types, spec_arc),
5142 &mut errors,
5143 );
5144 collect(
5145 check_expression(right, graph, inferred_types, resolved_types, spec_arc),
5146 &mut errors,
5147 );
5148
5149 let left_type =
5150 infer_expression_type(left, graph, inferred_types, resolved_types, spec_arc);
5151 let right_type =
5152 infer_expression_type(right, graph, inferred_types, resolved_types, spec_arc);
5153 let expr_source = expression
5154 .source_location
5155 .as_ref()
5156 .expect("BUG: expression missing source in check_expression");
5157 collect(
5158 check_logical_or_operands(graph, &left_type, &right_type, expr_source),
5159 &mut errors,
5160 );
5161 }
5162
5163 ExpressionKind::LogicalNegation(operand, _) => {
5164 collect(
5165 check_expression(operand, graph, inferred_types, resolved_types, spec_arc),
5166 &mut errors,
5167 );
5168
5169 let operand_type =
5170 infer_expression_type(operand, graph, inferred_types, resolved_types, spec_arc);
5171 let expr_source = expression
5172 .source_location
5173 .as_ref()
5174 .expect("BUG: expression missing source in check_expression");
5175 collect(
5176 check_logical_operand(graph, &operand_type, expr_source),
5177 &mut errors,
5178 );
5179 }
5180
5181 ExpressionKind::Comparison(left, op, right) => {
5182 collect(
5183 check_expression(left, graph, inferred_types, resolved_types, spec_arc),
5184 &mut errors,
5185 );
5186 collect(
5187 check_expression(right, graph, inferred_types, resolved_types, spec_arc),
5188 &mut errors,
5189 );
5190
5191 let left_type =
5192 infer_expression_type(left, graph, inferred_types, resolved_types, spec_arc);
5193 let right_type =
5194 infer_expression_type(right, graph, inferred_types, resolved_types, spec_arc);
5195 let expr_source = expression
5196 .source_location
5197 .as_ref()
5198 .expect("BUG: expression missing source in check_expression");
5199 collect(
5200 check_comparison_types(graph, &left_type, op, &right_type, expr_source),
5201 &mut errors,
5202 );
5203 }
5204
5205 ExpressionKind::Arithmetic(left, operator, right) => {
5206 collect(
5207 check_expression(left, graph, inferred_types, resolved_types, spec_arc),
5208 &mut errors,
5209 );
5210 collect(
5211 check_expression(right, graph, inferred_types, resolved_types, spec_arc),
5212 &mut errors,
5213 );
5214
5215 let left_type =
5216 infer_expression_type(left, graph, inferred_types, resolved_types, spec_arc);
5217 let right_type =
5218 infer_expression_type(right, graph, inferred_types, resolved_types, spec_arc);
5219 let expr_source = expression
5220 .source_location
5221 .as_ref()
5222 .expect("BUG: expression missing source in check_expression");
5223 collect(
5224 check_arithmetic_types(graph, &left_type, &right_type, operator, expr_source),
5225 &mut errors,
5226 );
5227 collect(
5228 arithmetic_plan_time_exactness_planning_errors(
5229 graph,
5230 left,
5231 right,
5232 &left_type,
5233 &right_type,
5234 operator,
5235 expr_source,
5236 ),
5237 &mut errors,
5238 );
5239 }
5240
5241 ExpressionKind::UnitConversion(source_expression, target) => {
5242 collect(
5243 check_expression(
5244 source_expression,
5245 graph,
5246 inferred_types,
5247 resolved_types,
5248 spec_arc,
5249 ),
5250 &mut errors,
5251 );
5252
5253 let source_type = infer_expression_type(
5254 source_expression,
5255 graph,
5256 inferred_types,
5257 resolved_types,
5258 spec_arc,
5259 );
5260 let expr_source = expression
5261 .source_location
5262 .as_ref()
5263 .expect("BUG: expression missing source in check_expression");
5264 collect(
5265 check_unit_conversion_types(
5266 graph,
5267 &source_type,
5268 target,
5269 resolved_types,
5270 expr_source,
5271 spec_arc,
5272 ),
5273 &mut errors,
5274 );
5275
5276 }
5279
5280 ExpressionKind::MathematicalComputation(_, operand) => {
5281 collect(
5282 check_expression(operand, graph, inferred_types, resolved_types, spec_arc),
5283 &mut errors,
5284 );
5285
5286 let operand_type =
5287 infer_expression_type(operand, graph, inferred_types, resolved_types, spec_arc);
5288 let expr_source = expression
5289 .source_location
5290 .as_ref()
5291 .expect("BUG: expression missing source in check_expression");
5292 collect(
5293 check_mathematical_operand(graph, &operand_type, expr_source),
5294 &mut errors,
5295 );
5296 }
5297
5298 ExpressionKind::Veto(_) => {}
5299
5300 ExpressionKind::ResultIsVeto(operand) => {
5301 collect(
5302 check_expression(operand, graph, inferred_types, resolved_types, spec_arc),
5303 &mut errors,
5304 );
5305 }
5306
5307 ExpressionKind::Now => {}
5308
5309 ExpressionKind::DateRelative(_, date_expr) => {
5310 collect(
5311 check_expression(date_expr, graph, inferred_types, resolved_types, spec_arc),
5312 &mut errors,
5313 );
5314
5315 let date_type =
5316 infer_expression_type(date_expr, graph, inferred_types, resolved_types, spec_arc);
5317 if !date_type.is_date() {
5318 let expr_source = expression
5319 .source_location
5320 .as_ref()
5321 .expect("BUG: expression missing source in check_expression");
5322 errors.push(engine_error_at_graph(
5323 graph,
5324 expr_source,
5325 format!(
5326 "Date sugar 'in past/future' requires a date expression, got type '{}'",
5327 date_type
5328 ),
5329 ));
5330 }
5331 }
5332
5333 ExpressionKind::DateCalendar(_, _, date_expr) => {
5334 collect(
5335 check_expression(date_expr, graph, inferred_types, resolved_types, spec_arc),
5336 &mut errors,
5337 );
5338
5339 let date_type =
5340 infer_expression_type(date_expr, graph, inferred_types, resolved_types, spec_arc);
5341 if !date_type.is_date() {
5342 let expr_source = expression
5343 .source_location
5344 .as_ref()
5345 .expect("BUG: expression missing source in check_expression");
5346 errors.push(engine_error_at_graph(
5347 graph,
5348 expr_source,
5349 format!(
5350 "Calendar sugar requires a date expression, got type '{}'",
5351 date_type
5352 ),
5353 ));
5354 }
5355 }
5356
5357 ExpressionKind::RangeLiteral(left, right) => {
5358 collect(
5359 check_expression(left, graph, inferred_types, resolved_types, spec_arc),
5360 &mut errors,
5361 );
5362 collect(
5363 check_expression(right, graph, inferred_types, resolved_types, spec_arc),
5364 &mut errors,
5365 );
5366
5367 let left_type =
5368 infer_expression_type(left, graph, inferred_types, resolved_types, spec_arc);
5369 let right_type =
5370 infer_expression_type(right, graph, inferred_types, resolved_types, spec_arc);
5371 let expr_source = expression
5372 .source_location
5373 .as_ref()
5374 .expect("BUG: expression missing source in check_expression");
5375
5376 let inferred_range_type = infer_range_type_from_endpoint_types(&left_type, &right_type);
5377 if inferred_range_type.is_undetermined() {
5378 errors.push(engine_error_at_graph(
5379 graph,
5380 expr_source,
5381 format!(
5382 "Cannot create a range from {} and {}.",
5383 left_type.name(),
5384 right_type.name()
5385 ),
5386 ));
5387 }
5388 }
5389
5390 ExpressionKind::PastFutureRange(_, offset_expr) => {
5391 collect(
5392 check_expression(offset_expr, graph, inferred_types, resolved_types, spec_arc),
5393 &mut errors,
5394 );
5395
5396 let offset_type =
5397 infer_expression_type(offset_expr, graph, inferred_types, resolved_types, spec_arc);
5398 if !offset_type.is_duration_like() && !offset_type.is_calendar() {
5399 let expr_source = expression
5400 .source_location
5401 .as_ref()
5402 .expect("BUG: expression missing source in check_expression");
5403 errors.push(engine_error_at_graph(
5404 graph,
5405 expr_source,
5406 format!(
5407 "Past/future range requires a duration or calendar expression, got type '{}'",
5408 offset_type.name()
5409 ),
5410 ));
5411 }
5412 }
5413
5414 ExpressionKind::RangeContainment(value, range) => {
5415 collect(
5416 check_expression(value, graph, inferred_types, resolved_types, spec_arc),
5417 &mut errors,
5418 );
5419 collect(
5420 check_expression(range, graph, inferred_types, resolved_types, spec_arc),
5421 &mut errors,
5422 );
5423
5424 let value_type =
5425 infer_expression_type(value, graph, inferred_types, resolved_types, spec_arc);
5426 let range_type =
5427 infer_expression_type(range, graph, inferred_types, resolved_types, spec_arc);
5428 let expr_source = expression
5429 .source_location
5430 .as_ref()
5431 .expect("BUG: expression missing source in check_expression");
5432
5433 if !range_type.is_range() {
5434 errors.push(engine_error_at_graph(
5435 graph,
5436 expr_source,
5437 format!(
5438 "Right side of 'in' must be a range, got type '{}'",
5439 range_type.name()
5440 ),
5441 ));
5442 } else {
5443 let compatible = (range_type.is_date_range() && value_type.is_date())
5444 || (range_type.is_number_range() && value_type.is_number())
5445 || (range_type.is_quantity_range()
5446 && value_type.is_quantity()
5447 && quantity_range_matches_quantity(&range_type, &value_type))
5448 || (range_type.is_ratio_range() && value_type.is_ratio())
5449 || (range_type.is_calendar_range() && value_type.is_calendar());
5450 if !compatible {
5451 errors.push(engine_error_at_graph(
5452 graph,
5453 expr_source,
5454 format!(
5455 "Cannot test whether {} is in {}.",
5456 value_type.name(),
5457 range_type.name()
5458 ),
5459 ));
5460 }
5461 }
5462 }
5463 }
5464
5465 if errors.is_empty() {
5466 Ok(())
5467 } else {
5468 Err(errors)
5469 }
5470}
5471
5472fn check_rule_types(
5478 graph: &Graph,
5479 execution_order: &[RulePath],
5480 inferred_types: &HashMap<RulePath, LemmaType>,
5481 resolved_types: &ResolvedTypesMap,
5482) -> Result<(), Vec<Error>> {
5483 let mut errors = Vec::new();
5484
5485 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
5486 if let Err(errs) = result {
5487 errors.extend(errs);
5488 }
5489 };
5490
5491 for rule_path in execution_order {
5492 let rule_node = match graph.rules().get(rule_path) {
5493 Some(node) => node,
5494 None => continue,
5495 };
5496 let branches = &rule_node.branches;
5497 let spec_arc = &rule_node.spec_arc;
5498
5499 if branches.is_empty() {
5500 continue;
5501 }
5502
5503 let (_, default_result) = &branches[0];
5504 collect(
5505 check_expression(
5506 default_result,
5507 graph,
5508 inferred_types,
5509 resolved_types,
5510 spec_arc,
5511 ),
5512 &mut errors,
5513 );
5514 let default_type = infer_expression_type(
5515 default_result,
5516 graph,
5517 inferred_types,
5518 resolved_types,
5519 spec_arc,
5520 );
5521
5522 if default_type.is_anonymous_quantity() {
5524 let decomp = default_type.quantity_type_decomposition();
5525 if !decomp.is_empty() {
5526 let default_source = default_result
5527 .source_location
5528 .as_ref()
5529 .expect("BUG: default branch result expression has no source location");
5530 errors.push(engine_error_at_graph(
5531 graph,
5532 default_source,
5533 format!(
5534 "Rule '{}' in spec '{}' returns an anonymous intermediate with unresolved \
5535 dimensions {:?}. Cast the result with 'as <unit>' (e.g., 'as mps') \
5536 or ensure all dimensions cancel.",
5537 rule_path.rule, spec_arc.name, decomp
5538 ),
5539 ));
5540 }
5541 }
5542
5543 let mut non_veto_type: Option<LemmaType> = None;
5544 if !default_type.vetoed() && !default_type.is_undetermined() {
5545 non_veto_type = Some(default_type.clone());
5546 }
5547
5548 for (branch_index, (condition, result)) in branches.iter().enumerate().skip(1) {
5549 if let Some(condition_expression) = condition {
5550 collect(
5551 check_expression(
5552 condition_expression,
5553 graph,
5554 inferred_types,
5555 resolved_types,
5556 spec_arc,
5557 ),
5558 &mut errors,
5559 );
5560 let condition_type = infer_expression_type(
5561 condition_expression,
5562 graph,
5563 inferred_types,
5564 resolved_types,
5565 spec_arc,
5566 );
5567 if !condition_type.is_boolean() && !condition_type.is_undetermined() {
5568 let condition_source = condition_expression
5569 .source_location
5570 .as_ref()
5571 .expect("BUG: condition expression missing source in check_rule_types");
5572 errors.push(engine_error_at_graph(
5573 graph,
5574 condition_source,
5575 format!(
5576 "Unless clause condition in rule '{}' must be boolean, got {:?}",
5577 rule_path.rule, condition_type
5578 ),
5579 ));
5580 }
5581 }
5582
5583 collect(
5584 check_expression(result, graph, inferred_types, resolved_types, spec_arc),
5585 &mut errors,
5586 );
5587 let result_type =
5588 infer_expression_type(result, graph, inferred_types, resolved_types, spec_arc);
5589
5590 if result_type.is_anonymous_quantity() {
5592 let decomp = result_type.quantity_type_decomposition();
5593 if !decomp.is_empty() {
5594 let branch_source = result
5595 .source_location
5596 .as_ref()
5597 .expect("BUG: unless branch result expression has no source location");
5598 errors.push(engine_error_at_graph(
5599 graph,
5600 branch_source,
5601 format!(
5602 "Unless clause {} in rule '{}' (spec '{}') returns an anonymous \
5603 intermediate with unresolved dimensions {:?}. Cast the result with \
5604 'as <unit>' or ensure all dimensions cancel.",
5605 branch_index, rule_path.rule, spec_arc.name, decomp
5606 ),
5607 ));
5608 }
5609 }
5610
5611 if !result_type.vetoed() && !result_type.is_undetermined() {
5612 if non_veto_type.is_none() {
5613 non_veto_type = Some(result_type.clone());
5614 } else if let Some(ref existing_type) = non_veto_type {
5615 if !existing_type.has_same_base_type(&result_type) {
5616 let Some(rule_node) = graph.rules().get(rule_path) else {
5617 unreachable!(
5618 "BUG: rule type validation referenced missing rule '{}'",
5619 rule_path.rule
5620 );
5621 };
5622 let rule_source = &rule_node.source;
5623 let default_expr = &branches[0].1;
5624
5625 let mut location_parts = vec![format!(
5626 "{}:{}:{}",
5627 rule_source.source_type, rule_source.span.line, rule_source.span.col
5628 )];
5629
5630 if let Some(loc) = &default_expr.source_location {
5631 location_parts.push(format!(
5632 "default branch at {}:{}:{}",
5633 loc.source_type, loc.span.line, loc.span.col
5634 ));
5635 }
5636 if let Some(loc) = &result.source_location {
5637 location_parts.push(format!(
5638 "unless clause {} at {}:{}:{}",
5639 branch_index, loc.source_type, loc.span.line, loc.span.col
5640 ));
5641 }
5642
5643 errors.push(Error::validation_with_context(
5644 format!("Type mismatch in rule '{}' in spec '{}' ({}): default branch returns {}, but unless clause {} returns {}. All branches must return the same primitive type.",
5645 rule_path.rule,
5646 spec_arc.name,
5647 location_parts.join(", "),
5648 existing_type.name(),
5649 branch_index,
5650 result_type.name()),
5651 Some(rule_source.clone()),
5652 None::<String>,
5653 Some(Arc::clone(&graph.main_spec)),
5654 None,
5655 ));
5656 }
5657 }
5658 }
5659 }
5660 }
5661
5662 if errors.is_empty() {
5663 Ok(())
5664 } else {
5665 Err(errors)
5666 }
5667}
5668
5669fn apply_inferred_types(graph: &mut Graph, inferred_types: HashMap<RulePath, LemmaType>) {
5677 for (rule_path, rule_type) in inferred_types {
5678 if let Some(rule_node) = graph.rules_mut().get_mut(&rule_path) {
5679 rule_node.rule_type = rule_type;
5680 }
5681 }
5682}
5683
5684fn infer_rule_types(
5688 graph: &Graph,
5689 execution_order: &[RulePath],
5690 resolved_types: &ResolvedTypesMap,
5691) -> HashMap<RulePath, LemmaType> {
5692 let mut computed_types: HashMap<RulePath, LemmaType> = HashMap::new();
5693
5694 for rule_path in execution_order {
5695 let rule_node = match graph.rules().get(rule_path) {
5696 Some(node) => node,
5697 None => continue,
5698 };
5699 let branches = &rule_node.branches;
5700 let spec_arc = &rule_node.spec_arc;
5701
5702 if branches.is_empty() {
5703 continue;
5704 }
5705
5706 let (_, default_result) = &branches[0];
5707 let default_type = infer_expression_type(
5708 default_result,
5709 graph,
5710 &computed_types,
5711 resolved_types,
5712 spec_arc,
5713 );
5714
5715 let mut non_veto_type: Option<LemmaType> = None;
5716 if !default_type.vetoed() && !default_type.is_undetermined() {
5717 non_veto_type = Some(default_type.clone());
5718 }
5719
5720 for (_branch_index, (_condition, result)) in branches.iter().enumerate().skip(1) {
5721 let result_type =
5722 infer_expression_type(result, graph, &computed_types, resolved_types, spec_arc);
5723 if !result_type.vetoed() && !result_type.is_undetermined() && non_veto_type.is_none() {
5724 non_veto_type = Some(result_type.clone());
5725 }
5726 }
5727
5728 let rule_type = non_veto_type.unwrap_or_else(LemmaType::veto_type);
5729 computed_types.insert(rule_path.clone(), rule_type);
5730 }
5731
5732 computed_types
5733}
5734
5735type UnitDecompLookup = HashMap<
5736 String,
5737 (
5738 String,
5739 BaseQuantityVector,
5740 crate::computation::rational::RationalInteger,
5741 ),
5742>;
5743
5744fn declared_quantity_decomposition(type_name: &str, lemma_type: &LemmaType) -> BaseQuantityVector {
5745 match &lemma_type.specifications {
5746 TypeSpecification::Quantity { traits, .. }
5747 if traits.contains(&semantics::QuantityTrait::Duration) =>
5748 {
5749 duration_decomposition()
5750 }
5751 _ => {
5752 let dimension_key = lemma_type
5753 .quantity_family_name()
5754 .unwrap_or(type_name)
5755 .to_string();
5756 [(dimension_key, 1i32)].into_iter().collect()
5757 }
5758 }
5759}
5760
5761fn sync_unit_index_from_resolved(
5762 resolved: &HashMap<String, LemmaType>,
5763 unit_index: &mut HashMap<String, LemmaType>,
5764) {
5765 let unit_index_updates: Vec<(String, LemmaType)> = unit_index
5766 .iter()
5767 .filter_map(|(unit_name, pre_decomp_type)| {
5768 let type_name = pre_decomp_type
5769 .name
5770 .as_deref()
5771 .or_else(|| pre_decomp_type.quantity_family_name())?;
5772 resolved
5773 .get(type_name)
5774 .or_else(|| {
5775 pre_decomp_type
5776 .quantity_family_name()
5777 .and_then(|family| resolved.get(family))
5778 })
5779 .map(|post_decomp_type| (unit_name.clone(), post_decomp_type.clone()))
5780 })
5781 .collect();
5782 for (unit_name, post_decomp_type) in unit_index_updates {
5783 unit_index.insert(unit_name, post_decomp_type);
5784 }
5785}
5786
5787fn repair_empty_simple_quantity_decomposition_in_unit_index(
5791 unit_index: &mut HashMap<String, LemmaType>,
5792) {
5793 for (_unit_key, lemma_type) in unit_index.iter_mut() {
5794 let base_decomp = {
5795 let TypeSpecification::Quantity {
5796 units,
5797 decomposition,
5798 ..
5799 } = &lemma_type.specifications
5800 else {
5801 continue;
5802 };
5803 if !decomposition.is_empty() {
5804 continue;
5805 }
5806 if units.is_empty() || units.iter().any(|u| !u.derived_quantity_factors.is_empty()) {
5807 continue;
5808 }
5809 let Some(ref type_name) = lemma_type.name else {
5810 continue;
5811 };
5812 if type_name.is_empty() {
5813 continue;
5814 }
5815 let candidate = declared_quantity_decomposition(type_name.as_str(), lemma_type);
5816 if candidate.is_empty() {
5817 continue;
5818 }
5819 Some(candidate)
5820 };
5821 let Some(base_decomp) = base_decomp else {
5822 continue;
5823 };
5824 let TypeSpecification::Quantity {
5825 units,
5826 decomposition,
5827 canonical_unit,
5828 ..
5829 } = &mut lemma_type.specifications
5830 else {
5831 continue;
5832 };
5833 let mut canonical = String::new();
5834 for unit in units.0.iter_mut() {
5835 unit.decomposition = base_decomp.clone();
5836 if unit.is_canonical_factor() && canonical.is_empty() {
5837 canonical = unit.name.clone();
5838 }
5839 }
5840 *decomposition = base_decomp;
5841 *canonical_unit = canonical;
5842 }
5843}
5844
5845fn owning_quantity_type_name_for_unit(
5846 unit_name: &str,
5847 lookup: &UnitDecompLookup,
5848 unit_index: &HashMap<String, LemmaType>,
5849) -> Option<String> {
5850 if let Some((owning_quantity_name, _, _)) = lookup.get(unit_name) {
5851 return Some(owning_quantity_name.clone());
5852 }
5853 unit_index.get(unit_name).and_then(|lemma_type| {
5854 lemma_type
5855 .name
5856 .clone()
5857 .or_else(|| lemma_type.quantity_family_name().map(str::to_string))
5858 })
5859}
5860
5861fn sort_derived_quantity_types_for_resolution(
5863 spec_name: &str,
5864 derived_quantity_type_names: Vec<String>,
5865 resolved: &HashMap<String, LemmaType>,
5866 lookup: &UnitDecompLookup,
5867 unit_index: &HashMap<String, LemmaType>,
5868 source_for: &dyn Fn(&str) -> Option<Source>,
5869) -> Result<Vec<String>, Error> {
5870 let derived_quantity_type_count = derived_quantity_type_names.len();
5871 if derived_quantity_type_count == 0 {
5872 return Ok(derived_quantity_type_names);
5873 }
5874
5875 let type_index: HashMap<&str, usize> = derived_quantity_type_names
5876 .iter()
5877 .enumerate()
5878 .map(|(index, name)| (name.as_str(), index))
5879 .collect();
5880
5881 let mut dependency_sets: Vec<HashSet<usize>> =
5882 vec![HashSet::new(); derived_quantity_type_count];
5883
5884 for (dependent_index, type_name) in derived_quantity_type_names.iter().enumerate() {
5885 let TypeSpecification::Quantity { units, .. } = &resolved[type_name].specifications else {
5886 continue;
5887 };
5888 for unit in units.iter() {
5889 for (factor_unit_name, _) in &unit.derived_quantity_factors {
5890 let Some(owning_quantity_name) =
5891 owning_quantity_type_name_for_unit(factor_unit_name, lookup, unit_index)
5892 else {
5893 continue;
5894 };
5895 let Some(dependency_index) = type_index.get(owning_quantity_name.as_str()).copied()
5896 else {
5897 continue;
5898 };
5899 if dependency_index == dependent_index {
5900 continue;
5901 }
5902 dependency_sets[dependent_index].insert(dependency_index);
5903 }
5904 }
5905 }
5906
5907 let mut in_degree = vec![0usize; derived_quantity_type_count];
5908 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); derived_quantity_type_count];
5909
5910 for (dependent_index, dependencies) in dependency_sets.iter().enumerate() {
5911 for &dependency_index in dependencies {
5912 in_degree[dependent_index] += 1;
5913 dependents[dependency_index].push(dependent_index);
5914 }
5915 }
5916
5917 let mut queue: VecDeque<usize> = (0..derived_quantity_type_count)
5918 .filter(|&index| in_degree[index] == 0)
5919 .collect();
5920 let mut sorted_indices: Vec<usize> = Vec::with_capacity(derived_quantity_type_count);
5921
5922 while let Some(index) = queue.pop_front() {
5923 sorted_indices.push(index);
5924 for &dependent_index in &dependents[index] {
5925 in_degree[dependent_index] -= 1;
5926 if in_degree[dependent_index] == 0 {
5927 queue.push_back(dependent_index);
5928 }
5929 }
5930 }
5931
5932 if sorted_indices.len() != derived_quantity_type_count {
5933 let mut cycle_type_names: Vec<String> = (0..derived_quantity_type_count)
5934 .filter(|&index| in_degree[index] > 0)
5935 .map(|index| derived_quantity_type_names[index].clone())
5936 .collect();
5937 cycle_type_names.sort();
5938 return Err(Error::validation(
5939 format!(
5940 "In spec '{}': circular compound quantity type dependency among: {}",
5941 spec_name,
5942 cycle_type_names.join(", ")
5943 ),
5944 source_for(&cycle_type_names[0]),
5945 None::<String>,
5946 ));
5947 }
5948
5949 Ok(sorted_indices
5950 .into_iter()
5951 .map(|index| derived_quantity_type_names[index].clone())
5952 .collect())
5953}
5954
5955fn resolve_quantity_decompositions(
5956 spec_name: &str,
5957 resolved: &mut HashMap<String, LemmaType>,
5958 unit_index: &mut HashMap<String, LemmaType>,
5959 type_sources: &HashMap<String, Source>,
5960) -> Vec<Error> {
5961 let mut errors: Vec<Error> = Vec::new();
5962
5963 let source_for = |type_name: &str| -> Option<Source> {
5964 type_sources
5965 .get(type_name)
5966 .or_else(|| type_sources.values().next())
5967 .cloned()
5968 };
5969
5970 let base_type_names: Vec<String> = resolved
5971 .iter()
5972 .filter_map(|(name, lt)| {
5973 if let TypeSpecification::Quantity { units, .. } = <.specifications {
5974 if units.iter().all(|u| u.derived_quantity_factors.is_empty()) {
5975 return Some(name.clone());
5976 }
5977 }
5978 None
5979 })
5980 .collect();
5981
5982 for type_name in &base_type_names {
5983 let base_decomp = {
5984 let lemma_type = resolved.get(type_name).unwrap();
5985 declared_quantity_decomposition(type_name, lemma_type)
5986 };
5987
5988 let lemma_type = resolved.get_mut(type_name).unwrap();
5989 let TypeSpecification::Quantity {
5990 units,
5991 decomposition,
5992 canonical_unit,
5993 ..
5994 } = &mut lemma_type.specifications
5995 else {
5996 continue;
5997 };
5998
5999 let mut canonical = String::new();
6000 for unit in units.0.iter_mut() {
6001 unit.decomposition = base_decomp.clone();
6002 if unit.is_canonical_factor() && canonical.is_empty() {
6003 canonical = unit.name.clone();
6004 }
6005 }
6006
6007 *decomposition = base_decomp;
6008 *canonical_unit = canonical;
6009 }
6010
6011 repair_empty_simple_quantity_decomposition_in_unit_index(unit_index);
6012
6013 let mut lookup = UnitDecompLookup::new();
6014
6015 for (unit_name, lemma_type) in unit_index.iter() {
6016 if let TypeSpecification::Quantity {
6017 decomposition,
6018 units,
6019 ..
6020 } = &lemma_type.specifications
6021 {
6022 if !decomposition.is_empty() {
6023 let quantity_name = lemma_type.name.clone().unwrap_or_default();
6024 let factor = units
6025 .iter()
6026 .find(|u| &u.name == unit_name)
6027 .map(|u| u.factor)
6028 .unwrap_or_else(crate::computation::rational::rational_one);
6029 lookup.insert(
6030 unit_name.clone(),
6031 (quantity_name, decomposition.clone(), factor),
6032 );
6033 }
6034 }
6035 }
6036
6037 for (type_name, lemma_type) in resolved.iter() {
6038 if let TypeSpecification::Quantity {
6039 units,
6040 decomposition,
6041 ..
6042 } = &lemma_type.specifications
6043 {
6044 if !decomposition.is_empty() {
6045 let is_defining_type = lemma_type
6046 .quantity_family_name()
6047 .map(|family| family == type_name.as_str())
6048 .unwrap_or(false);
6049 if !is_defining_type {
6050 continue;
6051 }
6052 for unit in units.iter() {
6053 lookup.insert(
6054 unit.name.clone(),
6055 (type_name.clone(), decomposition.clone(), unit.factor),
6056 );
6057 }
6058 }
6059 }
6060 }
6061
6062 let derived_quantity_type_names_unsorted: Vec<String> = resolved
6063 .iter()
6064 .filter_map(|(name, lemma_type)| {
6065 if let TypeSpecification::Quantity { units, .. } = &lemma_type.specifications {
6066 if units
6067 .iter()
6068 .any(|unit| !unit.derived_quantity_factors.is_empty())
6069 {
6070 return Some(name.clone());
6071 }
6072 }
6073 None
6074 })
6075 .collect();
6076
6077 let derived_quantity_type_names = match sort_derived_quantity_types_for_resolution(
6078 spec_name,
6079 derived_quantity_type_names_unsorted,
6080 resolved,
6081 &lookup,
6082 unit_index,
6083 &|type_name| source_for(type_name),
6084 ) {
6085 Ok(sorted) => sorted,
6086 Err(error) => {
6087 errors.push(error);
6088 return errors;
6089 }
6090 };
6091
6092 for type_name in &derived_quantity_type_names {
6093 let type_source = source_for(type_name);
6094
6095 let units_snapshot = match &resolved[type_name].specifications {
6096 TypeSpecification::Quantity { units, .. } => units.clone(),
6097 _ => continue,
6098 };
6099
6100 let mut resolved_type_decomp: Option<BaseQuantityVector> = None;
6101 let mut canonical = String::new();
6102 let mut unit_errors: Vec<Error> = Vec::new();
6103 let mut resolved_unit_factors: Vec<Option<crate::computation::rational::RationalInteger>> =
6104 vec![None; units_snapshot.len()];
6105
6106 for (unit_idx, unit) in units_snapshot.iter().enumerate() {
6107 if unit.derived_quantity_factors.is_empty() {
6108 let simple_decomp =
6109 declared_quantity_decomposition(type_name, &resolved[type_name]);
6110
6111 if let Some(existing) = &resolved_type_decomp {
6112 if existing != &simple_decomp {
6113 unit_errors.push(Error::validation(
6114 format!(
6115 "In spec '{}': quantity type '{}' has inconsistent unit decompositions. \
6116 Unit '{}' is a simple unit (decomposition {{{}: 1}}) but other units \
6117 have decomposition {:?}.",
6118 spec_name, type_name, unit.name, type_name, existing
6119 ),
6120 type_source.clone(),
6121 None::<String>,
6122 ));
6123 }
6124 } else {
6125 resolved_type_decomp = Some(simple_decomp);
6126 }
6127
6128 resolved_unit_factors[unit_idx] = Some(unit.factor);
6129
6130 if unit.is_canonical_factor() && canonical.is_empty() {
6131 canonical = unit.name.clone();
6132 }
6133 continue;
6134 }
6135
6136 match resolve_compound_unit(
6137 spec_name,
6138 type_name,
6139 &unit.name,
6140 unit.factor,
6141 &unit.derived_quantity_factors,
6142 &lookup,
6143 type_source.as_ref(),
6144 ) {
6145 Ok((unit_decomp, derived_factor)) => {
6146 if let Some(existing) = &resolved_type_decomp {
6147 if existing != &unit_decomp {
6148 unit_errors.push(Error::validation(
6149 format!(
6150 "In spec '{}': quantity type '{}' has inconsistent unit \
6151 decompositions. Unit '{}' resolved to {:?} but other units \
6152 resolved to {:?}. All units of a quantity must measure the same \
6153 physical quantity.",
6154 spec_name, type_name, unit.name, unit_decomp, existing
6155 ),
6156 type_source.clone(),
6157 None::<String>,
6158 ));
6159 }
6160 } else {
6161 resolved_type_decomp = Some(unit_decomp);
6162 }
6163
6164 resolved_unit_factors[unit_idx] = Some(derived_factor);
6165
6166 if derived_factor == crate::computation::rational::rational_one()
6167 && canonical.is_empty()
6168 {
6169 canonical = unit.name.clone();
6170 }
6171 }
6172 Err(err) => unit_errors.push(err),
6173 }
6174 }
6175
6176 if !unit_errors.is_empty() {
6177 errors.extend(unit_errors);
6178 continue;
6179 }
6180
6181 let type_decomp = match resolved_type_decomp {
6182 Some(d) => d,
6183 None => continue,
6184 };
6185
6186 if canonical.is_empty() {
6187 use crate::computation::rational::{checked_div, rational_is_zero};
6188
6189 let Some((normalizer_unit_index, normalizer_factor)) = resolved_unit_factors
6190 .iter()
6191 .enumerate()
6192 .find_map(|(unit_index, factor)| factor.map(|factor| (unit_index, factor)))
6193 else {
6194 errors.push(Error::validation(
6195 format!(
6196 "In spec '{}': quantity type '{}' has no unit with conversion factor 1. \
6197 Exactly one unit must have factor 1.",
6198 spec_name, type_name
6199 ),
6200 type_source.clone(),
6201 None::<String>,
6202 ));
6203 continue;
6204 };
6205
6206 if rational_is_zero(&normalizer_factor) {
6207 errors.push(Error::validation(
6208 format!(
6209 "In spec '{}': quantity type '{}' cannot normalize conversion factors because \
6210 unit '{}' has a zero conversion factor.",
6211 spec_name,
6212 type_name,
6213 units_snapshot.0[normalizer_unit_index].name
6214 ),
6215 type_source.clone(),
6216 None::<String>,
6217 ));
6218 continue;
6219 }
6220
6221 let mut normalization_failed = false;
6222 for (unit_index, resolved_factor) in resolved_unit_factors.iter_mut().enumerate() {
6223 let Some(factor) = resolved_factor.as_ref() else {
6224 continue;
6225 };
6226 match checked_div(factor, &normalizer_factor) {
6227 Ok(normalized_factor) => {
6228 *resolved_factor = Some(normalized_factor);
6229 }
6230 Err(error) => {
6231 normalization_failed = true;
6232 errors.push(Error::validation(
6233 format!(
6234 "In spec '{}': quantity type '{}' overflowed while normalizing \
6235 conversion factor for unit '{}': {}",
6236 spec_name, type_name, units_snapshot.0[unit_index].name, error
6237 ),
6238 type_source.clone(),
6239 None::<String>,
6240 ));
6241 }
6242 }
6243 }
6244
6245 if normalization_failed {
6246 continue;
6247 }
6248
6249 canonical = units_snapshot.0[normalizer_unit_index].name.clone();
6250 }
6251
6252 let lemma_type = resolved.get_mut(type_name).unwrap();
6253 let TypeSpecification::Quantity {
6254 units,
6255 decomposition,
6256 canonical_unit,
6257 ..
6258 } = &mut lemma_type.specifications
6259 else {
6260 continue;
6261 };
6262
6263 for (unit_idx, unit) in units.0.iter_mut().enumerate() {
6264 unit.decomposition = type_decomp.clone();
6265 if let Some(factor) = resolved_unit_factors[unit_idx] {
6266 unit.factor = factor;
6267 }
6268 }
6269 *decomposition = type_decomp.clone();
6270 *canonical_unit = canonical;
6271
6272 for unit in units.0.iter() {
6273 lookup.insert(
6274 unit.name.clone(),
6275 (type_name.clone(), type_decomp.clone(), unit.factor),
6276 );
6277 }
6278 }
6279
6280 for type_name in &base_type_names {
6281 let lemma_type = resolved.get(type_name).unwrap();
6282 let TypeSpecification::Quantity {
6283 units,
6284 canonical_unit,
6285 ..
6286 } = &lemma_type.specifications
6287 else {
6288 continue;
6289 };
6290
6291 if canonical_unit.is_empty() && !units.is_empty() {
6292 errors.push(Error::validation(
6293 format!(
6294 "In spec '{}': quantity type '{}' has no unit with conversion factor 1. \
6295 Exactly one unit must have factor 1.",
6296 spec_name, type_name
6297 ),
6298 source_for(type_name),
6299 None::<String>,
6300 ));
6301 }
6302 }
6303
6304 sync_unit_index_from_resolved(resolved, unit_index);
6305 repair_empty_simple_quantity_decomposition_in_unit_index(unit_index);
6306
6307 errors
6308}
6309
6310fn resolve_compound_unit(
6311 spec_name: &str,
6312 declaring_type_name: &str,
6313 unit_name: &str,
6314 prefix: crate::computation::rational::RationalInteger,
6315 factors: &[(String, i32)],
6316 lookup: &UnitDecompLookup,
6317 source: Option<&Source>,
6318) -> Result<
6319 (
6320 BaseQuantityVector,
6321 crate::computation::rational::RationalInteger,
6322 ),
6323 Error,
6324> {
6325 use crate::computation::rational::{checked_mul, checked_pow_i32};
6326
6327 let mut result: BaseQuantityVector = BaseQuantityVector::new();
6328 let mut derived_factor = prefix;
6329
6330 for (quantity_ref, exponent) in factors {
6331 if let Some(calendar_unit) = CalendarUnit::from_keyword(quantity_ref) {
6332 let calendar_factor = calendar_unit.canonical_factor();
6333 let calendar_decomp = calendar_decomposition();
6334 for (dim, &dim_exp) in &calendar_decomp {
6335 accumulate(&mut result, dim, dim_exp * exponent);
6336 }
6337 let calendar_rational = calendar_factor;
6338 let component_contribution =
6339 checked_pow_i32(&calendar_rational, *exponent).map_err(|error| {
6340 overflow_to_validation_error(
6341 spec_name,
6342 unit_name,
6343 declaring_type_name,
6344 quantity_ref,
6345 error,
6346 source,
6347 )
6348 })?;
6349 derived_factor =
6350 checked_mul(&derived_factor, &component_contribution).map_err(|error| {
6351 overflow_to_validation_error(
6352 spec_name,
6353 unit_name,
6354 declaring_type_name,
6355 quantity_ref,
6356 error,
6357 source,
6358 )
6359 })?;
6360 continue;
6361 }
6362
6363 let (owning_quantity_name, owning_decomp, unit_factor) =
6364 lookup.get(quantity_ref.as_str()).ok_or_else(|| {
6365 Error::validation(
6366 format!(
6367 "In spec '{}': unit '{}' in quantity type '{}' references '{}' which is not a \
6368 known unit of any in-scope quantity type. Add `uses <spec>` (or declare the \
6369 owning quantity type in this spec) so its units are in scope.",
6370 spec_name, unit_name, declaring_type_name, quantity_ref
6371 ),
6372 source.cloned(),
6373 None::<String>,
6374 )
6375 })?;
6376
6377 if owning_quantity_name == declaring_type_name {
6378 return Err(Error::validation(
6379 format!(
6380 "In spec '{}': unit '{}' in quantity type '{}' references unit '{}' which \
6381 belongs to the same quantity type. A quantity cannot reference its own units \
6382 in a compound expression.",
6383 spec_name, unit_name, declaring_type_name, quantity_ref
6384 ),
6385 source.cloned(),
6386 None::<String>,
6387 ));
6388 }
6389
6390 if owning_decomp.is_empty() {
6391 return Err(Error::validation(
6392 format!(
6393 "In spec '{}': unit '{}' in quantity type '{}' references '{}' whose owning \
6394 quantity type '{}' does not yet have a resolved decomposition. Ensure base \
6395 quantities are declared before derived quantities that depend on them.",
6396 spec_name, unit_name, declaring_type_name, quantity_ref, owning_quantity_name
6397 ),
6398 source.cloned(),
6399 None::<String>,
6400 ));
6401 }
6402
6403 for (dim, &dim_exp) in owning_decomp {
6404 accumulate(&mut result, dim, dim_exp * exponent);
6405 }
6406
6407 let component_contribution = checked_pow_i32(unit_factor, *exponent).map_err(|error| {
6408 overflow_to_validation_error(
6409 spec_name,
6410 unit_name,
6411 declaring_type_name,
6412 quantity_ref,
6413 error,
6414 source,
6415 )
6416 })?;
6417 derived_factor =
6418 checked_mul(&derived_factor, &component_contribution).map_err(|error| {
6419 overflow_to_validation_error(
6420 spec_name,
6421 unit_name,
6422 declaring_type_name,
6423 quantity_ref,
6424 error,
6425 source,
6426 )
6427 })?;
6428 }
6429
6430 Ok((result, derived_factor))
6431}
6432
6433fn overflow_to_validation_error(
6434 spec_name: &str,
6435 unit_name: &str,
6436 declaring_type_name: &str,
6437 quantity_ref: &str,
6438 failure: crate::computation::rational::NumericFailure,
6439 source: Option<&Source>,
6440) -> Error {
6441 Error::validation(
6442 format!(
6443 "In spec '{}': unit '{}' in quantity type '{}' overflowed while combining '{}': {}",
6444 spec_name, unit_name, declaring_type_name, quantity_ref, failure
6445 ),
6446 source.cloned(),
6447 None::<String>,
6448 )
6449}
6450
6451fn accumulate(result: &mut BaseQuantityVector, dim: &str, value: i32) {
6452 let entry = result.entry(dim.to_string()).or_insert(0);
6453 *entry += value;
6454 if *entry == 0 {
6455 result.remove(dim);
6456 }
6457}
6458
6459#[cfg(test)]
6460mod tests {
6461 use super::*;
6462
6463 use crate::parsing::ast::{BooleanValue, Reference, Span, Value};
6464
6465 fn test_source() -> Source {
6466 Source::new(
6467 crate::parsing::source::SourceType::Volatile,
6468 Span {
6469 start: 0,
6470 end: 0,
6471 line: 1,
6472 col: 0,
6473 },
6474 )
6475 }
6476
6477 fn build_graph(main_spec: &LemmaSpec, all_specs: &[LemmaSpec]) -> Result<Graph, Vec<Error>> {
6478 use crate::engine::Context;
6479 use crate::planning::discovery;
6480
6481 let mut ctx = Context::new();
6482 let repository = ctx.workspace();
6483 for s in all_specs {
6484 if let Err(e) = ctx.insert_spec(Arc::clone(&repository), Arc::new(s.clone())) {
6485 return Err(vec![e]);
6486 }
6487 }
6488 let effective = EffectiveDate::from_option(main_spec.effective_from().cloned());
6489 let main_spec_arc = ctx
6490 .spec_set(&repository, main_spec.name.as_str())
6491 .and_then(|ss| ss.get_exact(main_spec.effective_from()).cloned())
6492 .expect("main_spec must be in all_specs");
6493 let dag =
6494 discovery::build_dag_for_spec(&ctx, &main_spec_arc, &effective).map_err(
6495 |e| match e {
6496 discovery::DagError::Cycle(es) | discovery::DagError::Other(es) => es,
6497 },
6498 )?;
6499 match Graph::build(&ctx, &repository, &main_spec_arc, &dag, &effective) {
6500 Ok((graph, _types)) => Ok(graph),
6501 Err(errors) => Err(errors),
6502 }
6503 }
6504
6505 fn create_test_spec(name: &str) -> LemmaSpec {
6506 LemmaSpec::new(name.to_string())
6507 }
6508
6509 fn create_literal_data(name: &str, value: Value) -> LemmaData {
6510 LemmaData {
6511 reference: Reference {
6512 segments: Vec::new(),
6513 name: name.to_string(),
6514 },
6515 value: ParsedDataValue::Definition {
6516 base: None,
6517 constraints: None,
6518 value: Some(value),
6519 },
6520 source_location: test_source(),
6521 }
6522 }
6523
6524 fn create_literal_expr(value: Value) -> ast::Expression {
6525 ast::Expression {
6526 kind: ast::ExpressionKind::Literal(value),
6527 source_location: Some(test_source()),
6528 }
6529 }
6530
6531 #[test]
6532 fn should_reject_data_binding_into_non_spec_data() {
6533 let mut spec = create_test_spec("test");
6538 spec = spec.add_data(create_literal_data("x", Value::Number(1.into())));
6539
6540 spec = spec.add_data(LemmaData {
6542 reference: Reference::from_path(vec!["x".to_string(), "y".to_string()]),
6543 value: ParsedDataValue::Definition {
6544 base: None,
6545 constraints: None,
6546 value: Some(Value::Number(2.into())),
6547 },
6548 source_location: test_source(),
6549 });
6550
6551 let result = build_graph(&spec, &[spec.clone()]);
6552 assert!(
6553 result.is_err(),
6554 "Overriding x.y must fail when x is not a spec reference"
6555 );
6556 }
6557
6558 #[test]
6559 fn should_reject_data_and_rule_name_collision() {
6560 let mut spec = create_test_spec("test");
6565 spec = spec.add_data(create_literal_data("x", Value::Number(1.into())));
6566 spec = spec.add_rule(LemmaRule {
6567 name: "x".to_string(),
6568 expression: create_literal_expr(Value::Number(2.into())),
6569 unless_clauses: Vec::new(),
6570 source_location: test_source(),
6571 });
6572
6573 let result = build_graph(&spec, &[spec.clone()]);
6574 assert!(
6575 result.is_err(),
6576 "Data and rule name collisions should be rejected"
6577 );
6578 }
6579
6580 #[test]
6581 fn test_duplicate_data() {
6582 let mut spec = create_test_spec("test");
6583 spec = spec.add_data(create_literal_data(
6584 "age",
6585 Value::Number(rust_decimal::Decimal::from(25)),
6586 ));
6587 spec = spec.add_data(create_literal_data(
6588 "age",
6589 Value::Number(rust_decimal::Decimal::from(30)),
6590 ));
6591
6592 let result = build_graph(&spec, &[spec.clone()]);
6593 assert!(result.is_err(), "Should detect duplicate data");
6594
6595 let errors = result.unwrap_err();
6596 assert!(errors.iter().any(|e| {
6597 let s = e.to_string();
6598 s.contains("already used") && s.contains("age")
6599 }));
6600 }
6601
6602 #[test]
6603 fn test_duplicate_rule() {
6604 let mut spec = create_test_spec("test");
6605
6606 let rule1 = LemmaRule {
6607 name: "test_rule".to_string(),
6608 expression: create_literal_expr(Value::Boolean(BooleanValue::True)),
6609 unless_clauses: Vec::new(),
6610 source_location: test_source(),
6611 };
6612 let rule2 = LemmaRule {
6613 name: "test_rule".to_string(),
6614 expression: create_literal_expr(Value::Boolean(BooleanValue::False)),
6615 unless_clauses: Vec::new(),
6616 source_location: test_source(),
6617 };
6618
6619 spec = spec.add_rule(rule1);
6620 spec = spec.add_rule(rule2);
6621
6622 let result = build_graph(&spec, &[spec.clone()]);
6623 assert!(result.is_err(), "Should detect duplicate rule");
6624
6625 let errors = result.unwrap_err();
6626 assert!(errors.iter().any(
6627 |e| e.to_string().contains("Duplicate rule") && e.to_string().contains("test_rule")
6628 ));
6629 }
6630
6631 #[test]
6632 fn test_missing_data_reference() {
6633 let mut spec = create_test_spec("test");
6634
6635 let missing_data_expr = ast::Expression {
6636 kind: ast::ExpressionKind::Reference(Reference {
6637 segments: Vec::new(),
6638 name: "nonexistent".to_string(),
6639 }),
6640 source_location: Some(test_source()),
6641 };
6642
6643 let rule = LemmaRule {
6644 name: "test_rule".to_string(),
6645 expression: missing_data_expr,
6646 unless_clauses: Vec::new(),
6647 source_location: test_source(),
6648 };
6649 spec = spec.add_rule(rule);
6650
6651 let result = build_graph(&spec, &[spec.clone()]);
6652 assert!(result.is_err(), "Should detect missing data");
6653
6654 let errors = result.unwrap_err();
6655 assert!(errors
6656 .iter()
6657 .any(|e| e.to_string().contains("Reference 'nonexistent' not found")));
6658 }
6659
6660 #[test]
6661 fn test_missing_spec_reference() {
6662 let mut spec = create_test_spec("test");
6663
6664 let data = LemmaData {
6665 reference: Reference {
6666 segments: Vec::new(),
6667 name: "contract".to_string(),
6668 },
6669 value: ParsedDataValue::Import(crate::parsing::ast::SpecRef::same_repository(
6670 "nonexistent",
6671 )),
6672 source_location: test_source(),
6673 };
6674 spec = spec.add_data(data);
6675
6676 let result = build_graph(&spec, &[spec.clone()]);
6677 assert!(result.is_err(), "Should detect missing spec");
6678
6679 let errors = result.unwrap_err();
6680 assert!(
6681 errors.iter().any(|e| e.to_string().contains("nonexistent")),
6682 "Error should mention nonexistent spec: {:?}",
6683 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
6684 );
6685 }
6686
6687 #[test]
6688 fn test_data_reference_conversion() {
6689 let mut spec = create_test_spec("test");
6690 spec = spec.add_data(create_literal_data(
6691 "age",
6692 Value::Number(rust_decimal::Decimal::from(25)),
6693 ));
6694
6695 let age_expr = ast::Expression {
6696 kind: ast::ExpressionKind::Reference(Reference {
6697 segments: Vec::new(),
6698 name: "age".to_string(),
6699 }),
6700 source_location: Some(test_source()),
6701 };
6702
6703 let rule = LemmaRule {
6704 name: "test_rule".to_string(),
6705 expression: age_expr,
6706 unless_clauses: Vec::new(),
6707 source_location: test_source(),
6708 };
6709 spec = spec.add_rule(rule);
6710
6711 let result = build_graph(&spec, &[spec.clone()]);
6712 assert!(result.is_ok(), "Should build graph successfully");
6713
6714 let graph = result.unwrap();
6715 let rule_node = graph.rules().values().next().unwrap();
6716
6717 assert!(matches!(
6718 rule_node.branches[0].1.kind,
6719 ExpressionKind::DataPath(_)
6720 ));
6721 }
6722
6723 #[test]
6724 fn test_rule_reference_conversion() {
6725 let mut spec = create_test_spec("test");
6726
6727 let rule1_expr = ast::Expression {
6728 kind: ast::ExpressionKind::Reference(Reference {
6729 segments: Vec::new(),
6730 name: "age".to_string(),
6731 }),
6732 source_location: Some(test_source()),
6733 };
6734
6735 let rule1 = LemmaRule {
6736 name: "rule1".to_string(),
6737 expression: rule1_expr,
6738 unless_clauses: Vec::new(),
6739 source_location: test_source(),
6740 };
6741 spec = spec.add_rule(rule1);
6742
6743 let rule2_expr = ast::Expression {
6744 kind: ast::ExpressionKind::Reference(Reference {
6745 segments: Vec::new(),
6746 name: "rule1".to_string(),
6747 }),
6748 source_location: Some(test_source()),
6749 };
6750
6751 let rule2 = LemmaRule {
6752 name: "rule2".to_string(),
6753 expression: rule2_expr,
6754 unless_clauses: Vec::new(),
6755 source_location: test_source(),
6756 };
6757 spec = spec.add_rule(rule2);
6758
6759 spec = spec.add_data(create_literal_data(
6760 "age",
6761 Value::Number(rust_decimal::Decimal::from(25)),
6762 ));
6763
6764 let result = build_graph(&spec, &[spec.clone()]);
6765 assert!(result.is_ok(), "Should build graph successfully");
6766
6767 let graph = result.unwrap();
6768 let rule2_node = graph
6769 .rules()
6770 .get(&RulePath {
6771 segments: Vec::new(),
6772 rule: "rule2".to_string(),
6773 })
6774 .unwrap();
6775
6776 assert_eq!(rule2_node.depends_on_rules.len(), 1);
6777 assert!(matches!(
6778 rule2_node.branches[0].1.kind,
6779 ExpressionKind::RulePath(_)
6780 ));
6781 }
6782
6783 #[test]
6784 fn test_collect_multiple_errors() {
6785 let mut spec = create_test_spec("test");
6786 spec = spec.add_data(create_literal_data(
6787 "age",
6788 Value::Number(rust_decimal::Decimal::from(25)),
6789 ));
6790 spec = spec.add_data(create_literal_data(
6791 "age",
6792 Value::Number(rust_decimal::Decimal::from(30)),
6793 ));
6794
6795 let missing_data_expr = ast::Expression {
6796 kind: ast::ExpressionKind::Reference(Reference {
6797 segments: Vec::new(),
6798 name: "nonexistent".to_string(),
6799 }),
6800 source_location: Some(test_source()),
6801 };
6802
6803 let rule = LemmaRule {
6804 name: "test_rule".to_string(),
6805 expression: missing_data_expr,
6806 unless_clauses: Vec::new(),
6807 source_location: test_source(),
6808 };
6809 spec = spec.add_rule(rule);
6810
6811 let result = build_graph(&spec, &[spec.clone()]);
6812 assert!(result.is_err(), "Should collect multiple errors");
6813
6814 let errors = result.unwrap_err();
6815 assert!(errors.len() >= 2, "Should have at least 2 errors");
6816 assert!(errors
6817 .iter()
6818 .any(|e| e.to_string().contains("already used")));
6819 assert!(errors
6820 .iter()
6821 .any(|e| e.to_string().contains("Reference 'nonexistent' not found")));
6822 }
6823
6824 #[test]
6825 fn test_type_registration_collects_multiple_errors() {
6826 use crate::parsing::ast::{DataValue, ParentType, PrimitiveKind, SpecRef};
6827
6828 let type_source = Source::new(
6829 crate::parsing::source::SourceType::Volatile,
6830 Span {
6831 start: 0,
6832 end: 0,
6833 line: 1,
6834 col: 0,
6835 },
6836 );
6837 let spec_a = create_test_spec("spec_a")
6838 .with_source_type(crate::parsing::source::SourceType::Volatile)
6839 .add_data(LemmaData {
6840 reference: Reference::local("dep".to_string()),
6841 value: DataValue::Import(SpecRef::same_repository("spec_b")),
6842 source_location: type_source.clone(),
6843 })
6844 .add_data(LemmaData {
6845 reference: Reference::local("money".to_string()),
6846 value: DataValue::Definition {
6847 base: Some(ParentType::Primitive {
6848 primitive: PrimitiveKind::Number,
6849 }),
6850 constraints: None,
6851 value: None,
6852 },
6853 source_location: type_source.clone(),
6854 })
6855 .add_data(LemmaData {
6856 reference: Reference::local("money".to_string()),
6857 value: DataValue::Definition {
6858 base: Some(ParentType::Primitive {
6859 primitive: PrimitiveKind::Number,
6860 }),
6861 constraints: None,
6862 value: None,
6863 },
6864 source_location: type_source,
6865 });
6866
6867 let type_source_b = Source::new(
6868 crate::parsing::source::SourceType::Volatile,
6869 Span {
6870 start: 0,
6871 end: 0,
6872 line: 1,
6873 col: 0,
6874 },
6875 );
6876 let spec_b = create_test_spec("spec_b")
6877 .with_source_type(crate::parsing::source::SourceType::Volatile)
6878 .add_data(LemmaData {
6879 reference: Reference::local("length".to_string()),
6880 value: DataValue::Definition {
6881 base: Some(ParentType::Primitive {
6882 primitive: PrimitiveKind::Number,
6883 }),
6884 constraints: None,
6885 value: None,
6886 },
6887 source_location: type_source_b.clone(),
6888 })
6889 .add_data(LemmaData {
6890 reference: Reference::local("length".to_string()),
6891 value: DataValue::Definition {
6892 base: Some(ParentType::Primitive {
6893 primitive: PrimitiveKind::Number,
6894 }),
6895 constraints: None,
6896 value: None,
6897 },
6898 source_location: type_source_b,
6899 });
6900
6901 let mut sources = HashMap::new();
6902 sources.insert(
6903 crate::parsing::source::SourceType::Volatile.to_string(),
6904 "spec spec_a\nuses dep: spec_b\ndata money: number\ndata money: number".to_string(),
6905 );
6906 sources.insert(
6907 crate::parsing::source::SourceType::Volatile.to_string(),
6908 "spec spec_b\ndata length: number\ndata length: number".to_string(),
6909 );
6910
6911 let result = build_graph(&spec_a, &[spec_a.clone(), spec_b.clone()]);
6912 assert!(
6913 result.is_err(),
6914 "Should fail with duplicate type/data errors"
6915 );
6916 }
6917
6918 #[test]
6923 fn spec_ref_resolves_to_single_spec_by_name() {
6924 let code = r#"spec myspec
6925data x: 10
6926
6927spec consumer
6928uses m: myspec
6929rule result: m.x"#;
6930 let specs = crate::parse(
6931 code,
6932 crate::parsing::source::SourceType::Volatile,
6933 &crate::ResourceLimits::default(),
6934 )
6935 .unwrap()
6936 .into_flattened_specs();
6937 let consumer = specs.iter().find(|d| d.name == "consumer").unwrap();
6938
6939 let graph = build_graph(consumer, &specs).unwrap();
6940 let data_path = DataPath {
6941 segments: vec![PathSegment {
6942 data: "m".to_string(),
6943 spec: "myspec".to_string(),
6944 }],
6945 data: "x".to_string(),
6946 };
6947 assert!(
6948 graph.data.contains_key(&data_path),
6949 "Ref should resolve to myspec. Data: {:?}",
6950 graph.data.keys().collect::<Vec<_>>()
6951 );
6952 }
6953
6954 #[test]
6955 fn spec_ref_to_nonexistent_spec_is_error() {
6956 let code = r#"spec myspec
6957data x: 10
6958
6959spec consumer
6960uses m: nonexistent
6961rule result: m.x"#;
6962 let specs = crate::parse(
6963 code,
6964 crate::parsing::source::SourceType::Volatile,
6965 &crate::ResourceLimits::default(),
6966 )
6967 .unwrap()
6968 .into_flattened_specs();
6969 let consumer = specs.iter().find(|d| d.name == "consumer").unwrap();
6970 let result = build_graph(consumer, &specs);
6971 assert!(result.is_err(), "Should fail for non-existent spec");
6972 }
6973
6974 #[test]
6979 fn import_alias_registered_in_graph() {
6980 let code = r#"
6981spec inner
6982data x: number -> default 1
6983
6984spec outer
6985uses i: inner
6986rule r: i.x
6987"#;
6988 let specs = crate::parse(
6989 code,
6990 crate::parsing::source::SourceType::Volatile,
6991 &crate::ResourceLimits::default(),
6992 )
6993 .unwrap()
6994 .into_flattened_specs();
6995 let outer = specs.iter().find(|s| s.name == "outer").unwrap();
6996 let graph = build_graph(outer, &specs).expect("uses i: inner must plan");
6997
6998 let alias_path = DataPath {
6999 segments: Vec::new(),
7000 data: "i".to_string(),
7001 };
7002 match graph.data().get(&alias_path) {
7003 Some(DataDefinition::Import { spec, .. }) => {
7004 assert_eq!(spec.name, "inner");
7005 }
7006 other => panic!(
7007 "alias path 'i' must be DataDefinition::Import, got {:?}",
7008 other
7009 ),
7010 }
7011
7012 let nested_path = DataPath {
7013 segments: vec![PathSegment {
7014 data: "i".to_string(),
7015 spec: "inner".to_string(),
7016 }],
7017 data: "x".to_string(),
7018 };
7019 assert!(
7020 graph.data().contains_key(&nested_path),
7021 "nested data i.x must exist after nested build_spec"
7022 );
7023 }
7024
7025 #[test]
7026 fn self_reference_is_error() {
7027 let code = "spec myspec\nuses m: myspec";
7028 let specs = crate::parse(
7029 code,
7030 crate::parsing::source::SourceType::Volatile,
7031 &crate::ResourceLimits::default(),
7032 )
7033 .unwrap()
7034 .into_flattened_specs();
7035 let result = build_graph(&specs[0], &specs);
7036 assert!(result.is_err(), "Self-reference should be an error");
7037 let errors = result.unwrap_err();
7038 let joined: String = errors
7039 .iter()
7040 .map(|e| e.to_string())
7041 .collect::<Vec<_>>()
7042 .join(" ");
7043 assert!(
7044 joined.contains("cannot reference itself") && joined.contains("myspec"),
7045 "Error should name self-reference: {:?}",
7046 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
7047 );
7048 }
7049}
7050
7051#[derive(Debug, Clone)]
7058pub struct ResolvedSpecTypes {
7059 pub resolved: HashMap<String, LemmaType>,
7062
7063 pub declared_defaults: HashMap<String, ValueKind>,
7068
7069 pub unit_index: HashMap<String, LemmaType>,
7072}
7073
7074#[derive(Debug, Clone, PartialEq)]
7076pub(crate) struct DataTypeDef {
7077 pub parent: ParentType,
7078 pub constraints: Option<Vec<Constraint>>,
7079 pub source: crate::parsing::source::Source,
7080 pub name: String,
7081 pub bound_literal: Option<ast::Value>,
7083}
7084
7085#[derive(Debug, Clone)]
7089pub(crate) struct TypeResolver<'a> {
7090 data_types: Vec<(Arc<LemmaSpec>, HashMap<String, DataTypeDef>)>,
7091 context: &'a Context,
7092 all_registered_specs: Vec<(Arc<LemmaRepository>, Arc<LemmaSpec>)>,
7093}
7094
7095fn inferred_parent_type_from_literal(value: &ast::Value) -> ParentType {
7097 match value {
7098 ast::Value::Number(_) => ParentType::Primitive {
7099 primitive: PrimitiveKind::Number,
7100 },
7101 ast::Value::Text(_) => ParentType::Primitive {
7102 primitive: PrimitiveKind::Text,
7103 },
7104 ast::Value::Boolean(_) => ParentType::Primitive {
7105 primitive: PrimitiveKind::Boolean,
7106 },
7107 ast::Value::Date(_) => ParentType::Primitive {
7108 primitive: PrimitiveKind::Date,
7109 },
7110 ast::Value::Time(_) => ParentType::Primitive {
7111 primitive: PrimitiveKind::Time,
7112 },
7113 ast::Value::NumberWithUnit(_, _) => ParentType::Primitive {
7114 primitive: PrimitiveKind::Quantity,
7115 },
7116 ast::Value::Calendar(_, _) => ParentType::Primitive {
7117 primitive: PrimitiveKind::Calendar,
7118 },
7119 ast::Value::Range(left, right) => {
7120 let primitive = match (left.as_ref(), right.as_ref()) {
7121 (ast::Value::Number(_), ast::Value::Number(_)) => PrimitiveKind::NumberRange,
7122 (ast::Value::Date(_), ast::Value::Date(_)) => PrimitiveKind::DateRange,
7123 (
7124 ast::Value::NumberWithUnit(_, u1),
7125 ast::Value::NumberWithUnit(_, u2),
7126 ) if u1 == u2 && matches!(u1.as_str(), "percent" | "permille") => {
7127 PrimitiveKind::RatioRange
7128 }
7129 (ast::Value::NumberWithUnit(_, _), ast::Value::NumberWithUnit(_, _)) => {
7130 PrimitiveKind::QuantityRange
7131 }
7132 (ast::Value::Calendar(_, _), ast::Value::Calendar(_, _)) => {
7133 PrimitiveKind::CalendarRange
7134 }
7135 _ => unreachable!(
7136 "BUG: inferred_parent_type_from_literal called on invalid range literal; planning must validate range endpoint types first"
7137 ),
7138 };
7139 ParentType::Primitive { primitive }
7140 }
7141 }
7142}
7143
7144impl<'a> TypeResolver<'a> {
7145 pub fn new(context: &'a Context) -> Self {
7146 TypeResolver {
7147 data_types: Vec::new(),
7148 context,
7149 all_registered_specs: Vec::new(),
7150 }
7151 }
7152
7153 pub fn is_registered(&self, spec: &Arc<LemmaSpec>) -> bool {
7154 self.all_registered_specs
7155 .iter()
7156 .any(|(_, s)| Arc::ptr_eq(s, spec))
7157 }
7158
7159 pub fn register_all(
7161 &mut self,
7162 repository: &Arc<LemmaRepository>,
7163 spec: &Arc<LemmaSpec>,
7164 ) -> Vec<Error> {
7165 if !self
7166 .all_registered_specs
7167 .iter()
7168 .any(|(_, s)| Arc::ptr_eq(s, spec))
7169 {
7170 self.all_registered_specs
7171 .push((Arc::clone(repository), Arc::clone(spec)));
7172 }
7173
7174 let mut errors = Vec::new();
7175 for data in &spec.data {
7176 match &data.value {
7177 ParsedDataValue::Definition {
7178 base,
7179 constraints,
7180 value,
7181 } => {
7182 if matches!(
7183 (base.as_ref(), constraints.as_ref(), value.as_ref()),
7184 (None, None, Some(Value::NumberWithUnit(_, _)),)
7185 ) {
7186 continue;
7187 }
7188 let name = &data.reference.name;
7189 let parent = match (base.as_ref(), value.as_ref()) {
7190 (Some(b), _) => b.clone(),
7191 (None, Some(v)) => inferred_parent_type_from_literal(v),
7192 (None, None) => {
7193 errors.push(Error::validation_with_context(
7194 format!(
7195 "Data '{name}' in spec '{}' must declare a type or a literal value",
7196 spec.name
7197 ),
7198 Some(data.source_location.clone()),
7199 None::<String>,
7200 Some(Arc::clone(spec)),
7201 None,
7202 ));
7203 continue;
7204 }
7205 };
7206 let ftd = DataTypeDef {
7207 parent,
7208 constraints: constraints.clone(),
7209 source: data.source_location.clone(),
7210 name: name.clone(),
7211 bound_literal: value.clone(),
7212 };
7213 if let Err(e) = self.register_type(spec, ftd) {
7214 errors.push(e);
7215 }
7216 }
7217 ParsedDataValue::Fill(_) | ParsedDataValue::Import(_) => {}
7218 }
7219 }
7220 errors
7221 }
7222
7223 pub fn register_type(&mut self, spec: &Arc<LemmaSpec>, def: DataTypeDef) -> Result<(), Error> {
7225 let spec_types = if let Some(pos) = self
7226 .data_types
7227 .iter()
7228 .position(|(s, _)| Arc::ptr_eq(s, spec))
7229 {
7230 &mut self.data_types[pos].1
7231 } else {
7232 self.data_types.push((Arc::clone(spec), HashMap::new()));
7233 let last = self.data_types.len() - 1;
7234 &mut self.data_types[last].1
7235 };
7236 if spec_types.contains_key(&def.name) {
7237 return Err(Error::validation_with_context(
7238 format!(
7239 "The name '{}' is already used for data in this spec.",
7240 def.name
7241 ),
7242 Some(def.source.clone()),
7243 None::<String>,
7244 Some(Arc::clone(spec)),
7245 None,
7246 ));
7247 }
7248 spec_types.insert(def.name.clone(), def);
7249 Ok(())
7250 }
7251
7252 pub fn resolve_and_validate(
7255 &self,
7256 spec: &Arc<LemmaSpec>,
7257 at: &EffectiveDate,
7258 ) -> Result<ResolvedSpecTypes, Vec<Error>> {
7259 let mut resolved_types = self.resolve_types_internal(spec, at)?;
7260 let mut errors = Vec::new();
7261
7262 let type_sources: std::collections::HashMap<String, Source> = resolved_types
7264 .resolved
7265 .keys()
7266 .filter_map(|type_name| {
7267 self.data_types
7268 .iter()
7269 .find(|(s, _)| Arc::ptr_eq(s, spec))
7270 .and_then(|(_, defs)| defs.get(type_name.as_str()))
7271 .map(|ftd| (type_name.clone(), ftd.source.clone()))
7272 })
7273 .collect();
7274
7275 let decomp_errors = resolve_quantity_decompositions(
7278 &spec.name,
7279 &mut resolved_types.resolved,
7280 &mut resolved_types.unit_index,
7281 &type_sources,
7282 );
7283 errors.extend(decomp_errors);
7284
7285 for (type_name, lemma_type) in resolved_types.resolved.iter_mut() {
7286 let source = type_sources.get(type_name).cloned().unwrap_or_else(|| {
7287 unreachable!(
7288 "BUG: resolved type '{}' has no corresponding DataTypeDef in spec '{}'",
7289 type_name, spec.name
7290 )
7291 });
7292 if let Err(message) = semantics::finalize_quantity_unit_constraint_magnitudes(
7293 &mut lemma_type.specifications,
7294 resolved_types.declared_defaults.get(type_name),
7295 type_name,
7296 ) {
7297 errors.push(Error::validation_with_context(
7298 format!(
7299 "Type '{}' has invalid quantity unit constraints: {}",
7300 type_name, message
7301 ),
7302 Some(source),
7303 None::<String>,
7304 Some(Arc::clone(spec)),
7305 None,
7306 ));
7307 }
7308 }
7309
7310 sync_unit_index_from_resolved(&resolved_types.resolved, &mut resolved_types.unit_index);
7311
7312 for lemma_type in resolved_types.unit_index.values_mut() {
7313 let type_name = lemma_type
7314 .name
7315 .as_deref()
7316 .or_else(|| lemma_type.quantity_family_name())
7317 .map(str::to_string);
7318 let Some(type_name) = type_name else {
7319 continue;
7320 };
7321 if !lemma_type.is_quantity() {
7322 continue;
7323 }
7324 if let Err(message) = semantics::finalize_quantity_unit_constraint_magnitudes(
7325 &mut lemma_type.specifications,
7326 resolved_types.declared_defaults.get(type_name.as_str()),
7327 type_name.as_str(),
7328 ) {
7329 let source = type_sources
7330 .get(type_name.as_str())
7331 .cloned()
7332 .or_else(|| {
7333 self.data_types.iter().find_map(|(_, defs)| {
7334 defs.get(type_name.as_str()).map(|def| def.source.clone())
7335 })
7336 })
7337 .unwrap_or_else(|| {
7338 unreachable!(
7339 "BUG: quantity type '{}' in unit_index has no DataTypeDef source",
7340 type_name
7341 )
7342 });
7343 errors.push(Error::validation_with_context(
7344 format!(
7345 "Type '{}' has invalid quantity unit constraints: {}",
7346 type_name, message
7347 ),
7348 Some(source),
7349 None::<String>,
7350 Some(Arc::clone(spec)),
7351 None,
7352 ));
7353 }
7354 }
7355
7356 let mut validated_in_unit_index: HashSet<String> = HashSet::new();
7357 for lemma_type in resolved_types.unit_index.values() {
7358 let Some(type_name) = lemma_type.name.as_deref() else {
7359 continue;
7360 };
7361 if !lemma_type.is_quantity() || !validated_in_unit_index.insert(type_name.to_string()) {
7362 continue;
7363 }
7364 let source = type_sources
7365 .get(type_name)
7366 .cloned()
7367 .or_else(|| {
7368 self.data_types
7369 .iter()
7370 .find_map(|(_, defs)| defs.get(type_name).map(|def| def.source.clone()))
7371 })
7372 .unwrap_or_else(|| {
7373 unreachable!(
7374 "BUG: quantity type '{}' in unit_index has no DataTypeDef source",
7375 type_name
7376 )
7377 });
7378 errors.extend(validate_type_specifications(
7379 &lemma_type.specifications,
7380 resolved_types.declared_defaults.get(type_name),
7381 type_name,
7382 &source,
7383 Some(Arc::clone(spec)),
7384 ));
7385 }
7386
7387 for (type_name, lemma_type) in &resolved_types.resolved {
7388 let source = type_sources.get(type_name).cloned().unwrap_or_else(|| {
7389 unreachable!(
7390 "BUG: resolved type '{}' has no corresponding DataTypeDef in spec '{}'",
7391 type_name, spec.name
7392 )
7393 });
7394 let mut spec_errors = validate_type_specifications(
7395 &lemma_type.specifications,
7396 resolved_types.declared_defaults.get(type_name),
7397 type_name,
7398 &source,
7399 Some(Arc::clone(spec)),
7400 );
7401 errors.append(&mut spec_errors);
7402 }
7403
7404 if errors.is_empty() {
7405 Ok(resolved_types)
7406 } else {
7407 Err(errors)
7408 }
7409 }
7410
7411 fn resolve_types_internal(
7416 &self,
7417 spec: &Arc<LemmaSpec>,
7418 at: &EffectiveDate,
7419 ) -> Result<ResolvedSpecTypes, Vec<Error>> {
7420 let mut resolved = HashMap::new();
7421 let mut declared_defaults: HashMap<String, ValueKind> = HashMap::new();
7422 let mut visited: Vec<(Arc<LemmaSpec>, String)> = Vec::new();
7423
7424 if let Some((_, spec_types)) = self.data_types.iter().find(|(s, _)| Arc::ptr_eq(s, spec)) {
7425 for type_name in spec_types.keys() {
7426 match self.resolve_type_internal(spec, type_name, &mut visited, at) {
7427 Ok(Some((resolved_type, declared_default))) => {
7428 resolved.insert(type_name.clone(), resolved_type);
7429 if let Some(dv) = declared_default {
7430 declared_defaults.insert(type_name.clone(), dv);
7431 }
7432 }
7433 Ok(None) => {
7434 unreachable!(
7435 "BUG: registered type '{}' could not be resolved (spec='{}')",
7436 type_name, spec.name
7437 );
7438 }
7439 Err(es) => return Err(es),
7440 }
7441 visited.clear();
7442 }
7443 }
7444
7445 let mut unit_index_tmp: HashMap<String, (LemmaType, Option<DataTypeDef>)> = HashMap::new();
7447 let mut errors = Vec::new();
7448
7449 let prim_ratio = semantics::primitive_ratio();
7450 for unit in Self::extract_units_from_type(&prim_ratio.specifications) {
7451 unit_index_tmp.insert(unit, (prim_ratio.clone(), None));
7452 }
7453
7454 for (type_name, resolved_type) in &resolved {
7455 let data_type_def = self
7456 .data_types
7457 .iter()
7458 .find(|(s, _)| Arc::ptr_eq(s, spec))
7459 .and_then(|(_, defs)| defs.get(type_name.as_str()))
7460 .expect("BUG: type was resolved but not in registry");
7461 let e: Result<(), Error> = if resolved_type.is_quantity() {
7462 Self::add_quantity_units_to_index(
7463 spec,
7464 &mut unit_index_tmp,
7465 resolved_type,
7466 data_type_def,
7467 )
7468 } else if resolved_type.is_ratio() {
7469 Self::add_ratio_units_to_index(
7470 spec,
7471 &mut unit_index_tmp,
7472 resolved_type,
7473 data_type_def,
7474 )
7475 } else {
7476 Ok(())
7477 };
7478 if let Err(e) = e {
7479 errors.push(e);
7480 }
7481 }
7482
7483 for data_row in &spec.data {
7484 let ParsedDataValue::Import(spec_ref) = &data_row.value else {
7485 continue;
7486 };
7487 let (_, imported_spec) =
7488 match self.resolve_spec_for_import(spec, spec_ref, &data_row.source_location, at) {
7489 Ok(x) => x,
7490 Err(_) => {
7491 continue;
7495 }
7496 };
7497 let Some((_, imported_type_map)) = self
7498 .data_types
7499 .iter()
7500 .find(|(s, _)| Arc::ptr_eq(s, &imported_spec))
7501 else {
7502 continue;
7503 };
7504
7505 let mut import_visited: Vec<(Arc<LemmaSpec>, String)> = Vec::new();
7506 for (type_name, def) in imported_type_map.iter() {
7507 if matches!(def.parent, ParentType::Qualified { .. }) {
7508 continue;
7509 }
7510 match self.resolve_type_internal(
7511 &imported_spec,
7512 type_name.as_str(),
7513 &mut import_visited,
7514 at,
7515 ) {
7516 Ok(Some((resolved_type, _))) => {
7517 if resolved_type.is_quantity() {
7518 if let Err(e) = Self::add_quantity_units_to_index(
7519 spec,
7520 &mut unit_index_tmp,
7521 &resolved_type,
7522 def,
7523 ) {
7524 errors.push(e);
7525 }
7526 } else if resolved_type.is_ratio() {
7527 if let Err(e) = Self::add_ratio_units_to_index(
7528 spec,
7529 &mut unit_index_tmp,
7530 &resolved_type,
7531 def,
7532 ) {
7533 errors.push(e);
7534 }
7535 }
7536 }
7537 Ok(None) => {}
7538 Err(_) => {
7539 }
7542 }
7543 import_visited.clear();
7544 }
7545 }
7546
7547 if !errors.is_empty() {
7548 return Err(errors);
7549 }
7550
7551 let unit_index = unit_index_tmp
7552 .into_iter()
7553 .map(|(k, (lt, _))| (k, lt))
7554 .collect();
7555
7556 Ok(ResolvedSpecTypes {
7557 resolved,
7558 declared_defaults,
7559 unit_index,
7560 })
7561 }
7562
7563 fn resolve_type_internal(
7564 &self,
7565 spec: &Arc<LemmaSpec>,
7566 name: &str,
7567 visited: &mut Vec<(Arc<LemmaSpec>, String)>,
7568 at: &EffectiveDate,
7569 ) -> Result<Option<(LemmaType, Option<ValueKind>)>, Vec<Error>> {
7570 if visited
7571 .iter()
7572 .any(|(s, n)| Arc::ptr_eq(s, spec) && n == name)
7573 {
7574 let source_location = self
7575 .data_types
7576 .iter()
7577 .find(|(s, _)| Arc::ptr_eq(s, spec))
7578 .and_then(|(_, dt)| dt.get(name))
7579 .map(|ftd| ftd.source.clone())
7580 .unwrap_or_else(|| {
7581 unreachable!(
7582 "BUG: circular dependency detected for type '{}::{}' but type definition not found in registry",
7583 spec.name, name
7584 )
7585 });
7586 return Err(vec![Error::validation_with_context(
7587 format!(
7588 "Circular dependency detected in type resolution: {}::{}",
7589 spec.name, name
7590 ),
7591 Some(source_location),
7592 None::<String>,
7593 Some(Arc::clone(spec)),
7594 None,
7595 )]);
7596 }
7597 visited.push((Arc::clone(spec), name.to_string()));
7598
7599 let ftd = match self
7600 .data_types
7601 .iter()
7602 .find(|(s, _)| Arc::ptr_eq(s, spec))
7603 .and_then(|(_, dt)| dt.get(name))
7604 {
7605 Some(def) => def.clone(),
7606 None => {
7607 if let Some(pos) = visited
7608 .iter()
7609 .position(|(s, n)| Arc::ptr_eq(s, spec) && n == name)
7610 {
7611 visited.remove(pos);
7612 }
7613 return Ok(None);
7614 }
7615 };
7616
7617 let parent = ftd.parent.clone();
7618 let constraints = ftd.constraints.clone();
7619
7620 let (parent_specs, parent_declared_default) = match self.resolve_parent(
7621 spec,
7622 &parent,
7623 visited,
7624 &ftd.source,
7625 at,
7626 ) {
7627 Ok(Some(pair)) => pair,
7628 Ok(None) => {
7629 if let Some(pos) = visited
7630 .iter()
7631 .position(|(s, n)| Arc::ptr_eq(s, spec) && n == name)
7632 {
7633 visited.remove(pos);
7634 }
7635 return Err(vec![Error::validation_with_context(
7636 format!("Unknown parent '{}' for data definition. Parent must be defined before use. Valid primitive types are: boolean, quantity, number, ratio, text, date, time, duration, percent", parent),
7637 Some(ftd.source.clone()),
7638 None::<String>,
7639 Some(Arc::clone(spec)),
7640 None,
7641 )]);
7642 }
7643 Err(es) => {
7644 if let Some(pos) = visited
7645 .iter()
7646 .position(|(s, n)| Arc::ptr_eq(s, spec) && n == name)
7647 {
7648 visited.remove(pos);
7649 }
7650 return Err(es);
7651 }
7652 };
7653
7654 let mut declared_default = parent_declared_default;
7655 let final_specs = if let Some(constraints) = &constraints {
7656 let constraint_type_name = constraint_application_type_name(&parent, name);
7657 match apply_constraints_to_spec(
7658 spec,
7659 &constraint_type_name,
7660 parent_specs,
7661 constraints,
7662 &ftd.source,
7663 &mut declared_default,
7664 ) {
7665 Ok(specs) => specs,
7666 Err(errors) => {
7667 if let Some(pos) = visited
7668 .iter()
7669 .position(|(s, n)| Arc::ptr_eq(s, spec) && n == name)
7670 {
7671 visited.remove(pos);
7672 }
7673 return Err(errors);
7674 }
7675 }
7676 } else {
7677 parent_specs
7678 };
7679
7680 if let Some(pos) = visited
7681 .iter()
7682 .position(|(s, n)| Arc::ptr_eq(s, spec) && n == name)
7683 {
7684 visited.remove(pos);
7685 }
7686
7687 let extends = {
7688 let parent_display = parent.to_string();
7689 let import_target: Option<Arc<LemmaSpec>> =
7690 if let ParentType::Qualified { spec_alias, .. } = &parent {
7691 let spec_ref = ast::SpecRef::same_repository(spec_alias.clone());
7692 match self.resolve_spec_for_import(spec, &spec_ref, &ftd.source, at) {
7693 Ok((_, arc)) => Some(arc),
7694 Err(e) => return Err(vec![e]),
7695 }
7696 } else {
7697 None
7698 };
7699
7700 let lookup_for_family: Option<(Arc<LemmaSpec>, String)> = match &parent {
7701 ParentType::Primitive { .. } => None,
7702 ParentType::Custom { name } => Some((Arc::clone(spec), name.clone())),
7703 ParentType::Qualified { inner, .. } => {
7704 let target = import_target.as_ref().expect(
7705 "BUG: qualified parent missing resolved import target for family lookup",
7706 );
7707 match inner.as_ref() {
7708 ParentType::Custom { name } => Some((Arc::clone(target), name.clone())),
7709 ParentType::Primitive { .. } => None,
7710 ParentType::Qualified { .. } => {
7711 return Err(vec![Error::validation_with_context(
7712 "Nested qualified parent types are invalid",
7713 Some(ftd.source.clone()),
7714 None::<String>,
7715 Some(Arc::clone(spec)),
7716 None,
7717 )]);
7718 }
7719 }
7720 }
7721 };
7722
7723 let family = match &lookup_for_family {
7724 None => name.to_string(),
7725 Some((r, pn)) => match self.resolve_type_internal(r, pn.as_str(), visited, at) {
7726 Ok(Some((parent_type, _))) => parent_type
7727 .quantity_family_name()
7728 .map(String::from)
7729 .unwrap_or_else(|| name.to_string()),
7730 Ok(None) => name.to_string(),
7731 Err(es) => return Err(es),
7732 },
7733 };
7734
7735 let defining_spec = if let Some(ref arc) = import_target {
7736 TypeDefiningSpec::Import {
7737 spec: Arc::clone(arc),
7738 }
7739 } else {
7740 TypeDefiningSpec::Local
7741 };
7742
7743 TypeExtends::Custom {
7744 parent: parent_display,
7745 family,
7746 defining_spec,
7747 }
7748 };
7749
7750 let declared_default = match &ftd.bound_literal {
7751 Some(lit) => match semantics::parser_value_to_value_kind(lit, &final_specs) {
7752 Ok(vk) => Some(vk),
7753 Err(message) => {
7754 return Err(vec![Error::validation_with_context(
7755 message,
7756 Some(ftd.source.clone()),
7757 None::<String>,
7758 Some(Arc::clone(spec)),
7759 None,
7760 )]);
7761 }
7762 },
7763 None => declared_default,
7764 };
7765
7766 Ok(Some((
7767 LemmaType {
7768 name: Some(name.to_string()),
7769 specifications: final_specs,
7770 extends,
7771 },
7772 declared_default,
7773 )))
7774 }
7775
7776 fn resolve_parent(
7777 &self,
7778 spec: &Arc<LemmaSpec>,
7779 parent: &ParentType,
7780 visited: &mut Vec<(Arc<LemmaSpec>, String)>,
7781 source: &crate::parsing::source::Source,
7782 at: &EffectiveDate,
7783 ) -> Result<Option<(TypeSpecification, Option<ValueKind>)>, Vec<Error>> {
7784 match parent {
7785 ParentType::Primitive { primitive: kind } => {
7786 Ok(Some((semantics::type_spec_for_primitive(*kind), None)))
7787 }
7788 ParentType::Custom { name } => {
7789 let parent_name = name.as_str();
7790 let result = self.resolve_type_internal(spec, parent_name, visited, at);
7791 match result {
7792 Ok(Some((t, declared_default))) => {
7793 Ok(Some((t.specifications, declared_default)))
7794 }
7795 Ok(None) => {
7796 let type_exists = self
7797 .data_types
7798 .iter()
7799 .find(|(s, _)| Arc::ptr_eq(s, spec))
7800 .map(|(_, m)| m.contains_key(parent_name))
7801 .unwrap_or(false);
7802
7803 if !type_exists {
7804 if spec.data.iter().any(|d| {
7805 d.reference.is_local()
7806 && d.reference.name == parent_name
7807 && matches!(&d.value, ParsedDataValue::Import(_))
7808 }) {
7809 return Err(vec![Error::validation_with_context(
7810 format!(
7811 "'{}' names a spec import alias, not a type: use `data x: {}.TypeName` after `uses`",
7812 parent_name, parent_name
7813 ),
7814 Some(source.clone()),
7815 None::<String>,
7816 Some(Arc::clone(spec)),
7817 None,
7818 )]);
7819 }
7820 Err(vec![Error::validation_with_context(
7821 format!("Unknown parent '{}' for data definition. Parent must be defined before use. Valid primitive types are: boolean, quantity, number, ratio, text, date, time, duration, percent", parent),
7822 Some(source.clone()),
7823 None::<String>,
7824 Some(Arc::clone(spec)),
7825 None,
7826 )])
7827 } else {
7828 Ok(None)
7829 }
7830 }
7831 Err(es) => Err(es),
7832 }
7833 }
7834 ParentType::Qualified { spec_alias, inner } => {
7835 let spec_ref = ast::SpecRef::same_repository(spec_alias.clone());
7836 let (_, target_arc) =
7837 match self.resolve_spec_for_import(spec, &spec_ref, source, at) {
7838 Ok(x) => x,
7839 Err(e) => return Err(vec![e]),
7840 };
7841 match inner.as_ref() {
7842 ParentType::Primitive { primitive } => {
7843 Ok(Some((semantics::type_spec_for_primitive(*primitive), None)))
7844 }
7845 ParentType::Custom { name } => {
7846 let result =
7847 self.resolve_type_internal(&target_arc, name.as_str(), visited, at);
7848 match result {
7849 Ok(Some((t, declared_default))) => {
7850 Ok(Some((t.specifications, declared_default)))
7851 }
7852 Ok(None) => {
7853 let type_exists = self
7854 .data_types
7855 .iter()
7856 .find(|(s, _)| Arc::ptr_eq(s, &target_arc))
7857 .map(|(_, m)| m.contains_key(name.as_str()))
7858 .unwrap_or(false);
7859 if !type_exists {
7860 Err(vec![Error::validation_with_context(
7861 format!(
7862 "Type '{}' is not defined in spec '{}' (via import '{}')",
7863 name, target_arc.name, spec_alias
7864 ),
7865 Some(source.clone()),
7866 None::<String>,
7867 Some(Arc::clone(spec)),
7868 None,
7869 )])
7870 } else {
7871 Ok(None)
7872 }
7873 }
7874 Err(es) => Err(es),
7875 }
7876 }
7877 ParentType::Qualified { .. } => Err(vec![Error::validation_with_context(
7878 "Nested qualified parent types are invalid",
7879 Some(source.clone()),
7880 None::<String>,
7881 Some(Arc::clone(spec)),
7882 None,
7883 )]),
7884 }
7885 }
7886 }
7887 }
7888
7889 fn resolve_spec_for_import(
7890 &self,
7891 spec: &Arc<LemmaSpec>,
7892 from: &crate::parsing::ast::SpecRef,
7893 import_site: &crate::parsing::source::Source,
7894 at: &EffectiveDate,
7895 ) -> Result<(Arc<LemmaRepository>, Arc<LemmaSpec>), Error> {
7896 let consumer_repository = self
7897 .all_registered_specs
7898 .iter()
7899 .find(|(_, s)| Arc::ptr_eq(s, spec))
7900 .map(|(r, _)| Arc::clone(r))
7901 .unwrap_or_else(|| self.context.workspace());
7902 discovery::resolve_spec_ref(
7903 self.context,
7904 from,
7905 &consumer_repository,
7906 spec,
7907 at,
7908 Some(import_site.clone()),
7909 )
7910 }
7911
7912 fn add_quantity_units_to_index(
7917 spec: &Arc<LemmaSpec>,
7918 unit_index: &mut HashMap<String, (LemmaType, Option<DataTypeDef>)>,
7919 resolved_type: &LemmaType,
7920 defined_by: &DataTypeDef,
7921 ) -> Result<(), Error> {
7922 let units = Self::extract_units_from_type(&resolved_type.specifications);
7923 for unit in units {
7924 if let Some((existing_type, existing_def)) = unit_index.get(&unit) {
7925 let same_type = existing_def.as_ref() == Some(defined_by);
7926
7927 if same_type {
7928 return Err(Error::validation_with_context(
7929 format!(
7930 "Unit '{}' is defined more than once in type '{}'",
7931 unit, defined_by.name
7932 ),
7933 Some(defined_by.source.clone()),
7934 None::<String>,
7935 Some(Arc::clone(spec)),
7936 None,
7937 ));
7938 }
7939
7940 let existing_name: String = existing_def
7941 .as_ref()
7942 .map(|d| d.name.clone())
7943 .unwrap_or_else(|| existing_type.name());
7944 let current_extends_existing = resolved_type
7945 .extends
7946 .parent_name()
7947 .map(|p| p == existing_name.as_str())
7948 .unwrap_or(false);
7949 let existing_extends_current = existing_type
7950 .extends
7951 .parent_name()
7952 .map(|p| p == defined_by.name.as_str())
7953 .unwrap_or(false);
7954
7955 if existing_type.is_quantity()
7956 && (current_extends_existing || existing_extends_current)
7957 {
7958 if current_extends_existing {
7959 unit_index.insert(unit, (resolved_type.clone(), Some(defined_by.clone())));
7960 }
7961 continue;
7962 }
7963
7964 if existing_type.same_quantity_family(resolved_type) {
7965 continue;
7966 }
7967
7968 return Err(Error::validation_with_context(
7969 format!(
7970 "Ambiguous unit '{}'. Defined in multiple types: '{}' and '{}'",
7971 unit, existing_name, defined_by.name
7972 ),
7973 Some(defined_by.source.clone()),
7974 None::<String>,
7975 Some(Arc::clone(spec)),
7976 None,
7977 ));
7978 }
7979 unit_index.insert(unit, (resolved_type.clone(), Some(defined_by.clone())));
7980 }
7981 Ok(())
7982 }
7983
7984 fn add_ratio_units_to_index(
7985 spec: &Arc<LemmaSpec>,
7986 unit_index: &mut HashMap<String, (LemmaType, Option<DataTypeDef>)>,
7987 resolved_type: &LemmaType,
7988 defined_by: &DataTypeDef,
7989 ) -> Result<(), Error> {
7990 let units = Self::extract_units_from_type(&resolved_type.specifications);
7991 for unit in units {
7992 if let Some((existing_type, existing_def)) = unit_index.get(&unit) {
7993 if existing_type.is_ratio() {
7994 if existing_def.is_none() {
7995 unit_index.insert(
7996 unit.clone(),
7997 (resolved_type.clone(), Some(defined_by.clone())),
7998 );
7999 continue;
8000 }
8001 if existing_type.name() == resolved_type.name() {
8002 continue;
8003 }
8004 let existing_name: String = existing_def
8005 .as_ref()
8006 .map(|d| d.name.clone())
8007 .unwrap_or_else(|| existing_type.name());
8008 return Err(Error::validation_with_context(
8009 format!(
8010 "Ambiguous unit '{}'. Defined in multiple ratio types: '{}' and '{}'",
8011 unit, existing_name, defined_by.name
8012 ),
8013 Some(defined_by.source.clone()),
8014 None::<String>,
8015 Some(Arc::clone(spec)),
8016 None,
8017 ));
8018 }
8019 let existing_name: String = existing_def
8020 .as_ref()
8021 .map(|d| d.name.clone())
8022 .unwrap_or_else(|| existing_type.name());
8023 return Err(Error::validation_with_context(
8024 format!(
8025 "Ambiguous unit '{}'. Defined in multiple types: '{}' and '{}'",
8026 unit, existing_name, defined_by.name
8027 ),
8028 Some(defined_by.source.clone()),
8029 None::<String>,
8030 Some(Arc::clone(spec)),
8031 None,
8032 ));
8033 }
8034 unit_index.insert(unit, (resolved_type.clone(), Some(defined_by.clone())));
8035 }
8036 Ok(())
8037 }
8038
8039 fn extract_units_from_type(specs: &TypeSpecification) -> Vec<String> {
8040 match specs {
8041 TypeSpecification::Quantity { units, .. } => {
8042 units.iter().map(|unit| unit.name.clone()).collect()
8043 }
8044 TypeSpecification::Ratio { units, .. } => {
8045 units.iter().map(|unit| unit.name.clone()).collect()
8046 }
8047 _ => Vec::new(),
8048 }
8049 }
8050}
8051
8052#[cfg(test)]
8053mod type_resolution_tests {
8054 use super::*;
8055 use crate::computation::rational::RationalInteger;
8056 use crate::parse;
8057 use crate::parsing::ast::{
8058 CommandArg, LemmaSpec, ParentType, PrimitiveKind, TypeConstraintCommand,
8059 };
8060 use crate::ResourceLimits;
8061 use rust_decimal::Decimal;
8062 use std::sync::Arc;
8063
8064 fn test_context_and_effective(
8065 specs: &[Arc<LemmaSpec>],
8066 ) -> (&'static Context, &'static EffectiveDate) {
8067 use crate::engine::Context;
8068 let mut ctx = Context::new();
8069 let repository = ctx.workspace();
8070 for s in specs {
8071 ctx.insert_spec(Arc::clone(&repository), Arc::clone(s))
8072 .unwrap();
8073 }
8074 let ctx = Box::leak(Box::new(ctx));
8075 let eff = Box::leak(Box::new(EffectiveDate::Origin));
8076 (ctx, eff)
8077 }
8078
8079 fn dag_and_spec() -> (Vec<Arc<LemmaSpec>>, Arc<LemmaSpec>) {
8080 let spec = LemmaSpec::new("test_spec".to_string());
8081 let arc = Arc::new(spec);
8082 let dag = vec![Arc::clone(&arc)];
8083 (dag, arc)
8084 }
8085
8086 fn resolver_for_code(code: &str) -> (TypeResolver<'static>, Vec<Arc<LemmaSpec>>) {
8087 let specs = parse(
8088 code,
8089 crate::parsing::source::SourceType::Volatile,
8090 &ResourceLimits::default(),
8091 )
8092 .unwrap()
8093 .into_flattened_specs();
8094 let spec_arcs: Vec<Arc<LemmaSpec>> = specs.iter().map(|s| Arc::new(s.clone())).collect();
8095 let (ctx, _) = test_context_and_effective(&spec_arcs);
8096 let repository = ctx.workspace();
8097 let mut resolver = TypeResolver::new(ctx);
8098 for spec_arc in &spec_arcs {
8099 resolver.register_all(&repository, spec_arc);
8100 }
8101 (resolver, spec_arcs)
8102 }
8103
8104 fn resolver_single_spec(code: &str) -> (TypeResolver<'static>, Arc<LemmaSpec>) {
8105 let (resolver, spec_arcs) = resolver_for_code(code);
8106 let spec_arc = spec_arcs.into_iter().next().expect("at least one spec");
8107 (resolver, spec_arc)
8108 }
8109
8110 #[test]
8111 fn test_type_spec_for_primitive_covers_all_variants() {
8112 use crate::parsing::ast::PrimitiveKind;
8113 use crate::planning::semantics::type_spec_for_primitive;
8114
8115 for kind in [
8116 PrimitiveKind::Boolean,
8117 PrimitiveKind::Quantity,
8118 PrimitiveKind::QuantityRange,
8119 PrimitiveKind::Number,
8120 PrimitiveKind::NumberRange,
8121 PrimitiveKind::Percent,
8122 PrimitiveKind::Ratio,
8123 PrimitiveKind::RatioRange,
8124 PrimitiveKind::Text,
8125 PrimitiveKind::Date,
8126 PrimitiveKind::DateRange,
8127 PrimitiveKind::Time,
8128 PrimitiveKind::Calendar,
8129 PrimitiveKind::CalendarRange,
8130 ] {
8131 let spec = type_spec_for_primitive(kind);
8132 assert!(
8133 !matches!(
8134 spec,
8135 crate::planning::semantics::TypeSpecification::Undetermined
8136 ),
8137 "type_spec_for_primitive({:?}) returned Undetermined",
8138 kind
8139 );
8140 }
8141 }
8142
8143 #[test]
8144 fn test_register_data_type_def() {
8145 let (dag, spec_arc) = dag_and_spec();
8146 let (ctx, _) = test_context_and_effective(&dag);
8147 let mut resolver = TypeResolver::new(ctx);
8148 let ftd = DataTypeDef {
8149 parent: ParentType::Primitive {
8150 primitive: PrimitiveKind::Number,
8151 },
8152 constraints: Some(vec![
8153 (
8154 TypeConstraintCommand::Minimum,
8155 vec![CommandArg::Literal(crate::literals::Value::Number(
8156 Decimal::ZERO,
8157 ))],
8158 ),
8159 (
8160 TypeConstraintCommand::Maximum,
8161 vec![CommandArg::Literal(crate::literals::Value::Number(
8162 Decimal::from(150),
8163 ))],
8164 ),
8165 ]),
8166 source: crate::parsing::source::Source::new(
8167 crate::parsing::source::SourceType::Volatile,
8168 crate::parsing::ast::Span {
8169 start: 0,
8170 end: 0,
8171 line: 1,
8172 col: 0,
8173 },
8174 ),
8175 name: "age".to_string(),
8176 bound_literal: None,
8177 };
8178
8179 let result = resolver.register_type(&spec_arc, ftd);
8180 assert!(result.is_ok());
8181 let resolved = resolver
8182 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8183 .unwrap();
8184 assert!(resolved.resolved.contains_key("age"));
8185 }
8186
8187 #[test]
8188 fn test_register_duplicate_type_fails() {
8189 let (dag, spec_arc) = dag_and_spec();
8190 let (ctx, _) = test_context_and_effective(&dag);
8191 let mut resolver = TypeResolver::new(ctx);
8192 let ftd = DataTypeDef {
8193 parent: ParentType::Primitive {
8194 primitive: PrimitiveKind::Number,
8195 },
8196 constraints: None,
8197 source: crate::parsing::source::Source::new(
8198 crate::parsing::source::SourceType::Volatile,
8199 crate::parsing::ast::Span {
8200 start: 0,
8201 end: 0,
8202 line: 1,
8203 col: 0,
8204 },
8205 ),
8206 name: "money".to_string(),
8207 bound_literal: None,
8208 };
8209 resolver.register_type(&spec_arc, ftd.clone()).unwrap();
8210 let result = resolver.register_type(&spec_arc, ftd);
8211 assert!(result.is_err());
8212 }
8213
8214 #[test]
8215 fn test_resolve_custom_type_from_primitive() {
8216 let (dag, spec_arc) = dag_and_spec();
8217 let (ctx, _) = test_context_and_effective(&dag);
8218 let mut resolver = TypeResolver::new(ctx);
8219 let ftd = DataTypeDef {
8220 parent: ParentType::Primitive {
8221 primitive: PrimitiveKind::Number,
8222 },
8223 constraints: None,
8224 source: crate::parsing::source::Source::new(
8225 crate::parsing::source::SourceType::Volatile,
8226 crate::parsing::ast::Span {
8227 start: 0,
8228 end: 0,
8229 line: 1,
8230 col: 0,
8231 },
8232 ),
8233 name: "money".to_string(),
8234 bound_literal: None,
8235 };
8236
8237 resolver.register_type(&spec_arc, ftd).unwrap();
8238 let resolved = resolver
8239 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8240 .unwrap();
8241
8242 assert!(resolved.resolved.contains_key("money"));
8243 let money_type = resolved.resolved.get("money").unwrap();
8244 assert_eq!(money_type.name, Some("money".to_string()));
8245 }
8246
8247 #[test]
8248 fn test_child_quantity_type_keeps_declared_name_and_child_units() {
8249 let (resolver, spec_arc) = resolver_single_spec(
8250 r#"spec test
8251data length: quantity
8252 -> unit meter 1
8253data road_length: length
8254 -> unit kilometer 1000"#,
8255 );
8256
8257 let resolved_types = resolver
8258 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8259 .unwrap();
8260
8261 let road_length_type = resolved_types.resolved.get("road_length").unwrap();
8262 assert_eq!(road_length_type.name.as_deref(), Some("road_length"));
8263
8264 match &road_length_type.specifications {
8265 TypeSpecification::Quantity { units, .. } => {
8266 assert!(units.iter().any(|unit| unit.name == "kilometer"));
8267 }
8268 _ => panic!("Expected Quantity type specifications"),
8269 }
8270
8271 let kilometer_owner = resolved_types.unit_index.get("kilometer").unwrap();
8272 assert_eq!(kilometer_owner.name.as_deref(), Some("road_length"));
8273 }
8274
8275 #[test]
8276 fn test_type_definition_resolution() {
8277 let (resolver, spec_arc) = resolver_single_spec(
8278 r#"spec test
8279data dice: number -> minimum 0 -> maximum 6"#,
8280 );
8281
8282 let resolved_types = resolver
8283 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8284 .unwrap();
8285 let dice_type = resolved_types.resolved.get("dice").unwrap();
8286
8287 match &dice_type.specifications {
8288 TypeSpecification::Number {
8289 minimum, maximum, ..
8290 } => {
8291 assert_eq!(*minimum, Some(RationalInteger::new(0, 1)));
8292 assert_eq!(*maximum, Some(RationalInteger::new(6, 1)));
8293 }
8294 _ => panic!("Expected Number type specifications"),
8295 }
8296 }
8297
8298 #[test]
8299 fn test_type_definition_with_multiple_commands() {
8300 let (resolver, spec_arc) = resolver_single_spec(
8301 r#"spec test
8302data money: quantity -> decimals 2 -> unit eur 1.0 -> unit usd 1.18"#,
8303 );
8304
8305 let resolved_types = resolver
8306 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8307 .unwrap();
8308 let money_type = resolved_types.resolved.get("money").unwrap();
8309
8310 match &money_type.specifications {
8311 TypeSpecification::Quantity {
8312 decimals, units, ..
8313 } => {
8314 assert_eq!(*decimals, Some(2));
8315 assert_eq!(units.len(), 2);
8316 assert!(units.iter().any(|u| u.name == "eur"));
8317 assert!(units.iter().any(|u| u.name == "usd"));
8318 }
8319 _ => panic!("Expected Quantity type specifications"),
8320 }
8321 }
8322
8323 #[test]
8324 fn test_number_type_with_decimals() {
8325 let (resolver, spec_arc) = resolver_single_spec(
8326 r#"spec test
8327data price: number -> decimals 2 -> minimum 0"#,
8328 );
8329
8330 let resolved_types = resolver
8331 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8332 .unwrap();
8333 let price_type = resolved_types.resolved.get("price").unwrap();
8334
8335 match &price_type.specifications {
8336 TypeSpecification::Number {
8337 decimals, minimum, ..
8338 } => {
8339 assert_eq!(*decimals, Some(2));
8340 assert_eq!(*minimum, Some(RationalInteger::new(0, 1)));
8341 }
8342 _ => panic!("Expected Number type specifications with decimals"),
8343 }
8344 }
8345
8346 #[test]
8347 fn test_number_type_decimals_only() {
8348 let (resolver, spec_arc) = resolver_single_spec(
8349 r#"spec test
8350data precise_number: number -> decimals 4"#,
8351 );
8352
8353 let resolved_types = resolver
8354 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8355 .unwrap();
8356 let precise_type = resolved_types.resolved.get("precise_number").unwrap();
8357
8358 match &precise_type.specifications {
8359 TypeSpecification::Number { decimals, .. } => {
8360 assert_eq!(*decimals, Some(4));
8361 }
8362 _ => panic!("Expected Number type with decimals 4"),
8363 }
8364 }
8365
8366 #[test]
8367 fn test_quantity_type_decimals_only() {
8368 let (resolver, spec_arc) = resolver_single_spec(
8369 r#"spec test
8370data weight: quantity -> unit kg 1 -> decimals 3"#,
8371 );
8372
8373 let resolved_types = resolver
8374 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8375 .unwrap();
8376 let weight_type = resolved_types.resolved.get("weight").unwrap();
8377
8378 match &weight_type.specifications {
8379 TypeSpecification::Quantity { decimals, .. } => {
8380 assert_eq!(*decimals, Some(3));
8381 }
8382 _ => panic!("Expected Quantity type with decimals 3"),
8383 }
8384 }
8385
8386 #[test]
8387 fn test_ratio_type_accepts_optional_decimals_command() {
8388 let (resolver, spec_arc) = resolver_single_spec(
8389 r#"spec test
8390data ratio_type: ratio -> decimals 2"#,
8391 );
8392
8393 let resolved_types = resolver
8394 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8395 .unwrap();
8396 let ratio_type = resolved_types.resolved.get("ratio_type").unwrap();
8397
8398 match &ratio_type.specifications {
8399 TypeSpecification::Ratio { decimals, .. } => {
8400 assert_eq!(
8401 *decimals,
8402 Some(2),
8403 "ratio type should accept decimals command"
8404 );
8405 }
8406 _ => panic!("Expected Ratio type with decimals 2"),
8407 }
8408 }
8409
8410 #[test]
8411 fn test_ratio_type_with_default_command() {
8412 let (resolver, spec_arc) = resolver_single_spec(
8413 r#"spec test
8414data percentage: ratio -> minimum 0% -> maximum 100% -> default 50%"#,
8415 );
8416
8417 let resolved_types = resolver
8418 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8419 .unwrap();
8420 let percentage_type = resolved_types.resolved.get("percentage").unwrap();
8421
8422 match &percentage_type.specifications {
8423 TypeSpecification::Ratio {
8424 minimum, maximum, ..
8425 } => {
8426 assert_eq!(
8427 *minimum,
8428 Some(RationalInteger::new(0, 1)),
8429 "ratio type should have minimum 0"
8430 );
8431 assert_eq!(
8432 *maximum,
8433 Some(RationalInteger::new(1, 1)),
8434 "ratio type should have maximum 1"
8435 );
8436 }
8437 _ => panic!("Expected Ratio type with minimum and maximum"),
8438 }
8439
8440 let declared = resolved_types
8441 .declared_defaults
8442 .get("percentage")
8443 .expect("declared default must be tracked for percentage");
8444 match declared {
8445 ValueKind::Ratio(v, unit) => {
8446 assert_eq!(*v, RationalInteger::new(1, 2));
8447 assert_eq!(unit.as_deref(), Some("percent"));
8448 }
8449 other => panic!("expected Ratio declared default, got {:?}", other),
8450 }
8451 }
8452
8453 #[test]
8454 fn test_quantity_extension_chain_same_family_units_allowed() {
8455 let (resolver, spec_arc) = resolver_single_spec(
8456 r#"spec test
8457data money: quantity -> unit eur 1
8458data money2: money -> unit usd 1.24"#,
8459 );
8460
8461 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8462 assert!(
8463 result.is_ok(),
8464 "Quantity extension chain should resolve: {:?}",
8465 result.err()
8466 );
8467
8468 let resolved = result.unwrap();
8469 assert!(
8470 resolved.unit_index.contains_key("eur"),
8471 "eur should be in unit_index"
8472 );
8473 assert!(
8474 resolved.unit_index.contains_key("usd"),
8475 "usd should be in unit_index"
8476 );
8477 let eur_type = resolved.unit_index.get("eur").unwrap();
8478 let usd_type = resolved.unit_index.get("usd").unwrap();
8479 assert_eq!(
8480 eur_type.name.as_deref(),
8481 Some("money2"),
8482 "more derived type (money2) should own inherited eur"
8483 );
8484 assert_eq!(
8485 usd_type.name.as_deref(),
8486 Some("money2"),
8487 "usd defined on money2 should be owned by money2"
8488 );
8489 }
8490
8491 #[test]
8492 fn test_invalid_parent_type_in_named_type_should_error() {
8493 let (resolver, spec_arc) = resolver_single_spec(
8494 r#"spec test
8495data invalid: nonexistent_type -> minimum 0"#,
8496 );
8497
8498 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8499 assert!(result.is_err(), "Should reject invalid parent type");
8500
8501 let errs = result.unwrap_err();
8502 assert!(!errs.is_empty(), "expected at least one error");
8503 let error_msg = errs[0].to_string();
8504 assert!(
8505 error_msg.contains("Unknown parent") && error_msg.contains("nonexistent_type"),
8506 "Error should mention unknown type. Got: {}",
8507 error_msg
8508 );
8509 }
8510
8511 #[test]
8512 fn test_invalid_primitive_type_name_should_error() {
8513 let (resolver, spec_arc) = resolver_single_spec(
8514 r#"spec test
8515data invalid: choice -> option "a""#,
8516 );
8517
8518 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8519 assert!(result.is_err(), "Should reject invalid type base 'choice'");
8520
8521 let errs = result.unwrap_err();
8522 assert!(!errs.is_empty(), "expected at least one error");
8523 let error_msg = errs[0].to_string();
8524 assert!(
8525 error_msg.contains("Unknown parent") && error_msg.contains("choice"),
8526 "Error should mention unknown type 'choice'. Got: {}",
8527 error_msg
8528 );
8529 }
8530
8531 #[test]
8532 fn test_quantity_extension_overwrites_parent_units() {
8533 let (resolver, spec_arc) = resolver_single_spec(
8534 r#"spec test
8535data money: quantity
8536 -> unit eur 1.00
8537 -> unit usd 0.84
8538
8539data money2: money
8540 -> unit eur 1.20
8541 -> unit usd 1.21
8542 -> unit gbp 1.30"#,
8543 );
8544
8545 let resolved = resolver
8546 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8547 .unwrap();
8548 let money2 = resolved.resolved.get("money2").unwrap();
8549 match &money2.specifications {
8550 TypeSpecification::Quantity { units, .. } => {
8551 assert_eq!(units.len(), 3);
8552 let eur = units.iter().find(|u| u.name == "eur").unwrap();
8553 let usd = units.iter().find(|u| u.name == "usd").unwrap();
8554 let gbp = units.iter().find(|u| u.name == "gbp").unwrap();
8555 assert_eq!(
8556 crate::commit_rational_to_decimal(&eur.factor).unwrap(),
8557 Decimal::from_str_exact("1.20").unwrap()
8558 );
8559 assert_eq!(
8560 crate::commit_rational_to_decimal(&usd.factor).unwrap(),
8561 Decimal::from_str_exact("1.21").unwrap()
8562 );
8563 assert_eq!(
8564 crate::commit_rational_to_decimal(&gbp.factor).unwrap(),
8565 Decimal::from_str_exact("1.30").unwrap()
8566 );
8567 }
8568 other => panic!("Expected Quantity type specifications, got {:?}", other),
8569 }
8570 }
8571
8572 #[test]
8573 fn test_spec_level_unit_ambiguity_errors_are_reported() {
8574 let (resolver, spec_arc) = resolver_single_spec(
8575 r#"spec test
8576data money_a: quantity
8577 -> unit eur 1.00
8578 -> unit usd 0.84
8579
8580data money_b: quantity
8581 -> unit eur 1.00
8582 -> unit usd 1.20
8583
8584data length_a: quantity
8585 -> unit meter 1.0
8586
8587data length_b: quantity
8588 -> unit meter 1.0"#,
8589 );
8590
8591 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8592 assert!(
8593 result.is_err(),
8594 "Expected ambiguous unit definitions to error"
8595 );
8596
8597 let errs = result.unwrap_err();
8598 assert!(!errs.is_empty(), "expected at least one error");
8599 let error_msg = errs
8600 .iter()
8601 .map(ToString::to_string)
8602 .collect::<Vec<_>>()
8603 .join("; ");
8604 assert!(
8605 error_msg.contains("eur") || error_msg.contains("usd") || error_msg.contains("meter"),
8606 "Error should mention at least one ambiguous unit. Got: {}",
8607 error_msg
8608 );
8609 }
8610
8611 #[test]
8612 fn test_ratio_unit_cross_family_collision_errors() {
8613 let (resolver, spec_arc) = resolver_single_spec(
8614 r#"spec test
8615data q: quantity
8616 -> unit foo 1
8617
8618data r: ratio
8619 -> unit foo 100"#,
8620 );
8621
8622 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8623 assert!(
8624 result.is_err(),
8625 "quantity and ratio must not share a unit name"
8626 );
8627 let error_msg = result
8628 .unwrap_err()
8629 .iter()
8630 .map(ToString::to_string)
8631 .collect::<Vec<_>>()
8632 .join("; ");
8633 assert!(
8634 error_msg.contains("foo"),
8635 "expected cross-family collision on 'foo', got: {}",
8636 error_msg
8637 );
8638 }
8639
8640 #[test]
8641 fn test_duplicate_ratio_unit_across_unrelated_types_errors() {
8642 let (resolver, spec_arc) = resolver_single_spec(
8643 r#"spec test
8644data spread_a: ratio
8645 -> unit basis_points 10000
8646
8647data spread_b: ratio
8648 -> unit basis_points 10000"#,
8649 );
8650
8651 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8652 assert!(
8653 result.is_err(),
8654 "unrelated ratio types must not define the same unit name"
8655 );
8656 let error_msg = result
8657 .unwrap_err()
8658 .iter()
8659 .map(ToString::to_string)
8660 .collect::<Vec<_>>()
8661 .join("; ");
8662 assert!(
8663 error_msg.contains("spread_a") && error_msg.contains("spread_b"),
8664 "expected duplicate ratio unit between types, got: {}",
8665 error_msg
8666 );
8667 }
8668
8669 #[test]
8670 fn test_number_type_cannot_have_units() {
8671 let (resolver, spec_arc) = resolver_single_spec(
8672 r#"spec test
8673data price: number
8674 -> unit eur 1.00"#,
8675 );
8676
8677 let result = resolver.resolve_types_internal(&spec_arc, &EffectiveDate::Origin);
8678 assert!(result.is_err(), "Number types must reject unit commands");
8679
8680 let errs = result.unwrap_err();
8681 assert!(!errs.is_empty(), "expected at least one error");
8682 let error_msg = errs[0].to_string();
8683 assert!(
8684 error_msg.contains("unit") && error_msg.contains("number"),
8685 "Error should mention units are invalid on number. Got: {}",
8686 error_msg
8687 );
8688 }
8689
8690 #[test]
8691 fn test_extending_type_inherits_units() {
8692 let (resolver, spec_arc) = resolver_single_spec(
8693 r#"spec test
8694data money: quantity
8695 -> unit eur 1.00
8696 -> unit usd 0.84
8697
8698data my_money: money
8699 -> unit gbp 1.30"#,
8700 );
8701
8702 let resolved = resolver
8703 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8704 .unwrap();
8705 let my_money_type = resolved.resolved.get("my_money").unwrap();
8706
8707 match &my_money_type.specifications {
8708 TypeSpecification::Quantity { units, .. } => {
8709 assert_eq!(units.len(), 3);
8710 assert!(units.iter().any(|u| u.name == "eur"));
8711 assert!(units.iter().any(|u| u.name == "usd"));
8712 assert!(units.iter().any(|u| u.name == "gbp"));
8713 }
8714 other => panic!("Expected Quantity type specifications, got {:?}", other),
8715 }
8716 }
8717
8718 #[test]
8719 fn test_value_copy_quantity_binding_overwrites_unit_factor() {
8720 let (resolver, spec_arc) = resolver_single_spec(
8721 r#"spec test
8722data source_quantity: quantity
8723 -> unit usd 1.00
8724
8725data z: source_quantity
8726 -> unit usd 0.84"#,
8727 );
8728
8729 let resolved = resolver
8730 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8731 .unwrap();
8732 let z = resolved.resolved.get("z").unwrap();
8733 match &z.specifications {
8734 TypeSpecification::Quantity { units, .. } => {
8735 assert_eq!(units.len(), 1);
8736 let usd = units.iter().find(|u| u.name == "usd").unwrap();
8737 assert_eq!(
8738 crate::commit_rational_to_decimal(&usd.factor).unwrap(),
8739 Decimal::from_str_exact("0.84").unwrap()
8740 );
8741 }
8742 other => panic!("Expected Quantity type specifications, got {:?}", other),
8743 }
8744 }
8745
8746 #[test]
8747 fn test_duplicate_unit_in_same_type_last_wins() {
8748 let (resolver, spec_arc) = resolver_single_spec(
8749 r#"spec test
8750data money: quantity
8751 -> unit eur 1.00
8752 -> unit eur 1.19"#,
8753 );
8754
8755 let resolved = resolver
8756 .resolve_types_internal(&spec_arc, &EffectiveDate::Origin)
8757 .unwrap();
8758 let money = resolved.resolved.get("money").unwrap();
8759 match &money.specifications {
8760 TypeSpecification::Quantity { units, .. } => {
8761 assert_eq!(units.len(), 1);
8762 let eur = units.iter().find(|u| u.name == "eur").unwrap();
8763 assert_eq!(
8764 crate::commit_rational_to_decimal(&eur.factor).unwrap(),
8765 Decimal::from_str_exact("1.19").unwrap()
8766 );
8767 }
8768 other => panic!("Expected Quantity type specifications, got {:?}", other),
8769 }
8770 }
8771}
8772
8773pub fn validate_type_specifications(
8787 specs: &TypeSpecification,
8788 declared_default: Option<&ValueKind>,
8789 type_name: &str,
8790 source: &Source,
8791 spec_context: Option<Arc<LemmaSpec>>,
8792) -> Vec<Error> {
8793 let mut errors = Vec::new();
8794
8795 match specs {
8796 TypeSpecification::Quantity {
8797 minimum,
8798 maximum,
8799 decimals,
8800 units,
8801 ..
8802 } => {
8803 if let (Some(min), Some(max)) = (minimum, maximum) {
8805 match (
8806 semantics::quantity_declared_bound_canonical(min, units, type_name, "minimum"),
8807 semantics::quantity_declared_bound_canonical(max, units, type_name, "maximum"),
8808 ) {
8809 (Ok(min_canonical), Ok(max_canonical)) => {
8810 if min_canonical > max_canonical {
8811 errors.push(Error::validation_with_context(
8812 format!(
8813 "Type '{}' has invalid range: minimum {} {} is greater than maximum {} {}",
8814 type_name,
8815 min.0,
8816 min.1,
8817 max.0,
8818 max.1
8819 ),
8820 Some(source.clone()),
8821 None::<String>,
8822 spec_context.clone(),
8823 None,
8824 ));
8825 }
8826 }
8827 (Err(message), _) | (_, Err(message)) => {
8828 errors.push(Error::validation_with_context(
8829 format!(
8830 "Type '{}' has invalid quantity bound: {}",
8831 type_name, message
8832 ),
8833 Some(source.clone()),
8834 None::<String>,
8835 spec_context.clone(),
8836 None,
8837 ));
8838 }
8839 }
8840 }
8841
8842 if minimum.is_some() {
8843 for unit in units.iter() {
8844 if unit.minimum.is_none() {
8845 errors.push(Error::validation_with_context(
8846 format!(
8847 "Type '{}' has minimum bound but unit '{}' is missing per-unit minimum after planning",
8848 type_name, unit.name
8849 ),
8850 Some(source.clone()),
8851 None::<String>,
8852 spec_context.clone(),
8853 None,
8854 ));
8855 }
8856 }
8857 }
8858 if maximum.is_some() {
8859 for unit in units.iter() {
8860 if unit.maximum.is_none() {
8861 errors.push(Error::validation_with_context(
8862 format!(
8863 "Type '{}' has maximum bound but unit '{}' is missing per-unit maximum after planning",
8864 type_name, unit.name
8865 ),
8866 Some(source.clone()),
8867 None::<String>,
8868 spec_context.clone(),
8869 None,
8870 ));
8871 }
8872 }
8873 }
8874 if declared_default.is_some() {
8875 for unit in units.iter() {
8876 if unit.default_magnitude.is_none() {
8877 errors.push(Error::validation_with_context(
8878 format!(
8879 "Type '{}' has default but unit '{}' is missing per-unit default after planning",
8880 type_name, unit.name
8881 ),
8882 Some(source.clone()),
8883 None::<String>,
8884 spec_context.clone(),
8885 None,
8886 ));
8887 }
8888 }
8889 }
8890
8891 if let Some(d) = decimals {
8893 if *d > 28 {
8894 errors.push(Error::validation_with_context(
8895 format!(
8896 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
8897 type_name, d
8898 ),
8899 Some(source.clone()),
8900 None::<String>,
8901 spec_context.clone(),
8902 None,
8903 ));
8904 }
8905 }
8906
8907 if let Some(ValueKind::Quantity(_def_value, def_unit, _def_decomp)) = declared_default {
8908 if !units.iter().any(|u| u.name == *def_unit) {
8909 errors.push(Error::validation_with_context(
8910 format!(
8911 "Type '{}' default unit '{}' is not a valid unit. Valid units: {}",
8912 type_name,
8913 def_unit,
8914 units
8915 .iter()
8916 .map(|u| u.name.clone())
8917 .collect::<Vec<_>>()
8918 .join(", ")
8919 ),
8920 Some(source.clone()),
8921 None::<String>,
8922 spec_context.clone(),
8923 None,
8924 ));
8925 }
8926 }
8927
8928 if units.is_empty() {
8930 errors.push(Error::validation_with_context(
8931 format!(
8932 "Type '{}' is a quantity type but has no units. Quantity types must define at least one unit (e.g. -> unit eur 1).",
8933 type_name
8934 ),
8935 Some(source.clone()),
8936 None::<String>,
8937 spec_context.clone(),
8938 None,
8939 ));
8940 }
8941
8942 if !units.is_empty() {
8944 let mut seen_names: Vec<String> = Vec::new();
8945 for unit in units.iter() {
8946 if unit.name.trim().is_empty() {
8948 errors.push(Error::validation_with_context(
8949 format!(
8950 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
8951 type_name
8952 ),
8953 Some(source.clone()),
8954 None::<String>,
8955 spec_context.clone(),
8956 None,
8957 ));
8958 }
8959
8960 let lower_name = unit.name.to_lowercase();
8962 if seen_names
8963 .iter()
8964 .any(|seen| seen.to_lowercase() == lower_name)
8965 {
8966 errors.push(Error::validation_with_context(
8967 format!("Type '{}' has duplicate unit name '{}' (case-insensitive). Unit names must be unique within a type.", type_name, unit.name),
8968 Some(source.clone()),
8969 None::<String>,
8970 spec_context.clone(),
8971 None,
8972 ));
8973 } else {
8974 seen_names.push(unit.name.clone());
8975 }
8976
8977 if !unit.is_positive_factor() {
8978 let factor = unit.factor.reduced();
8979 errors.push(Error::validation_with_context(
8980 format!("Type '{}' has unit '{}' with invalid value {}/{}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, factor.numer(), factor.denom()),
8981 Some(source.clone()),
8982 None::<String>,
8983 spec_context.clone(),
8984 None,
8985 ));
8986 }
8987 }
8988 }
8989 }
8990 TypeSpecification::Number {
8991 minimum,
8992 maximum,
8993 decimals,
8994 ..
8995 } => {
8996 if let (Some(min), Some(max)) = (minimum, maximum) {
8998 if min > max {
8999 errors.push(Error::validation_with_context(
9000 format!(
9001 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
9002 type_name, min, max
9003 ),
9004 Some(source.clone()),
9005 None::<String>,
9006 spec_context.clone(),
9007 None,
9008 ));
9009 }
9010 }
9011
9012 if let Some(d) = decimals {
9014 if *d > 28 {
9015 errors.push(Error::validation_with_context(
9016 format!(
9017 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
9018 type_name, d
9019 ),
9020 Some(source.clone()),
9021 None::<String>,
9022 spec_context.clone(),
9023 None,
9024 ));
9025 }
9026 }
9027
9028 if let Some(ValueKind::Number(def)) = declared_default {
9029 if let Some(min) = minimum {
9030 if *def < *min {
9031 errors.push(Error::validation_with_context(
9032 format!(
9033 "Type '{}' default value {} is less than minimum {}",
9034 type_name, def, min
9035 ),
9036 Some(source.clone()),
9037 None::<String>,
9038 spec_context.clone(),
9039 None,
9040 ));
9041 }
9042 }
9043 if let Some(max) = maximum {
9044 if *def > *max {
9045 errors.push(Error::validation_with_context(
9046 format!(
9047 "Type '{}' default value {} is greater than maximum {}",
9048 type_name, def, max
9049 ),
9050 Some(source.clone()),
9051 None::<String>,
9052 spec_context.clone(),
9053 None,
9054 ));
9055 }
9056 }
9057 }
9058 }
9060
9061 TypeSpecification::Ratio {
9062 minimum,
9063 maximum,
9064 decimals,
9065 units,
9066 ..
9067 } => {
9068 if let Some(d) = decimals {
9070 if *d > 28 {
9071 errors.push(Error::validation_with_context(
9072 format!(
9073 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
9074 type_name, d
9075 ),
9076 Some(source.clone()),
9077 None::<String>,
9078 spec_context.clone(),
9079 None,
9080 ));
9081 }
9082 }
9083
9084 if let (Some(min), Some(max)) = (minimum, maximum) {
9086 if min > max {
9087 errors.push(Error::validation_with_context(
9088 format!(
9089 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
9090 type_name, min, max
9091 ),
9092 Some(source.clone()),
9093 None::<String>,
9094 spec_context.clone(),
9095 None,
9096 ));
9097 }
9098 }
9099
9100 if let Some(ValueKind::Ratio(def, _)) = declared_default {
9101 if let Some(min) = minimum {
9102 if *def < *min {
9103 errors.push(Error::validation_with_context(
9104 format!(
9105 "Type '{}' default value {} is less than minimum {}",
9106 type_name, def, min
9107 ),
9108 Some(source.clone()),
9109 None::<String>,
9110 spec_context.clone(),
9111 None,
9112 ));
9113 }
9114 }
9115 if let Some(max) = maximum {
9116 if *def > *max {
9117 errors.push(Error::validation_with_context(
9118 format!(
9119 "Type '{}' default value {} is greater than maximum {}",
9120 type_name, def, max
9121 ),
9122 Some(source.clone()),
9123 None::<String>,
9124 spec_context.clone(),
9125 None,
9126 ));
9127 }
9128 }
9129 }
9130
9131 if !units.is_empty() {
9135 let mut seen_names: Vec<String> = Vec::new();
9136 for unit in units.iter() {
9137 if unit.name.trim().is_empty() {
9139 errors.push(Error::validation_with_context(
9140 format!(
9141 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
9142 type_name
9143 ),
9144 Some(source.clone()),
9145 None::<String>,
9146 spec_context.clone(),
9147 None,
9148 ));
9149 }
9150
9151 let lower_name = unit.name.to_lowercase();
9153 if seen_names
9154 .iter()
9155 .any(|seen| seen.to_lowercase() == lower_name)
9156 {
9157 errors.push(Error::validation_with_context(
9158 format!("Type '{}' has duplicate unit name '{}' (case-insensitive). Unit names must be unique within a type.", type_name, unit.name),
9159 Some(source.clone()),
9160 None::<String>,
9161 spec_context.clone(),
9162 None,
9163 ));
9164 } else {
9165 seen_names.push(unit.name.clone());
9166 }
9167
9168 if *unit.value.numer() <= 0 {
9169 let factor = unit.value.reduced();
9170 errors.push(Error::validation_with_context(
9171 format!("Type '{}' has unit '{}' with invalid value {}/{}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, factor.numer(), factor.denom()),
9172 Some(source.clone()),
9173 None::<String>,
9174 spec_context.clone(),
9175 None,
9176 ));
9177 }
9178 }
9179 }
9180 }
9181
9182 TypeSpecification::Text {
9183 length, options, ..
9184 } => {
9185 if let Some(ValueKind::Text(def)) = declared_default {
9186 let def_len = def.len();
9187
9188 if let Some(len) = length {
9189 if def_len != *len {
9190 errors.push(Error::validation_with_context(
9191 format!("Type '{}' default value length {} does not match required length {}", type_name, def_len, len),
9192 Some(source.clone()),
9193 None::<String>,
9194 spec_context.clone(),
9195 None,
9196 ));
9197 }
9198 }
9199 if !options.is_empty() && !options.contains(def) {
9200 errors.push(Error::validation_with_context(
9201 format!(
9202 "Type '{}' default value '{}' is not in allowed options: {:?}",
9203 type_name, def, options
9204 ),
9205 Some(source.clone()),
9206 None::<String>,
9207 spec_context.clone(),
9208 None,
9209 ));
9210 }
9211 }
9212 }
9213
9214 TypeSpecification::Date {
9215 minimum,
9216 maximum,
9217 ..
9218 } => {
9219 if let (Some(min), Some(max)) = (minimum, maximum) {
9221 let min_sem = semantics::date_time_to_semantic(min);
9222 let max_sem = semantics::date_time_to_semantic(max);
9223 if semantics::compare_semantic_dates(&min_sem, &max_sem) == Ordering::Greater {
9224 errors.push(Error::validation_with_context(
9225 format!(
9226 "Type '{}' has invalid date range: minimum {} is after maximum {}",
9227 type_name, min, max
9228 ),
9229 Some(source.clone()),
9230 None::<String>,
9231 spec_context.clone(),
9232 None,
9233 ));
9234 }
9235 }
9236
9237 if let Some(ValueKind::Date(def)) = declared_default {
9238 if let Some(min) = minimum {
9239 let min_sem = semantics::date_time_to_semantic(min);
9240 if semantics::compare_semantic_dates(def, &min_sem) == Ordering::Less {
9241 errors.push(Error::validation_with_context(
9242 format!(
9243 "Type '{}' default date {} is before minimum {}",
9244 type_name, def, min
9245 ),
9246 Some(source.clone()),
9247 None::<String>,
9248 spec_context.clone(),
9249 None,
9250 ));
9251 }
9252 }
9253 if let Some(max) = maximum {
9254 let max_sem = semantics::date_time_to_semantic(max);
9255 if semantics::compare_semantic_dates(def, &max_sem) == Ordering::Greater {
9256 errors.push(Error::validation_with_context(
9257 format!(
9258 "Type '{}' default date {} is after maximum {}",
9259 type_name, def, max
9260 ),
9261 Some(source.clone()),
9262 None::<String>,
9263 spec_context.clone(),
9264 None,
9265 ));
9266 }
9267 }
9268 }
9269 }
9270
9271 TypeSpecification::Time {
9272 minimum,
9273 maximum,
9274 ..
9275 } => {
9276 if let (Some(min), Some(max)) = (minimum, maximum) {
9278 let min_sem = semantics::time_to_semantic(min);
9279 let max_sem = semantics::time_to_semantic(max);
9280 if semantics::compare_semantic_times(&min_sem, &max_sem) == Ordering::Greater {
9281 errors.push(Error::validation_with_context(
9282 format!(
9283 "Type '{}' has invalid time range: minimum {} is after maximum {}",
9284 type_name, min, max
9285 ),
9286 Some(source.clone()),
9287 None::<String>,
9288 spec_context.clone(),
9289 None,
9290 ));
9291 }
9292 }
9293
9294 if let Some(ValueKind::Time(def)) = declared_default {
9295 if let Some(min) = minimum {
9296 let min_sem = semantics::time_to_semantic(min);
9297 if semantics::compare_semantic_times(def, &min_sem) == Ordering::Less {
9298 errors.push(Error::validation_with_context(
9299 format!(
9300 "Type '{}' default time {} is before minimum {}",
9301 type_name, def, min
9302 ),
9303 Some(source.clone()),
9304 None::<String>,
9305 spec_context.clone(),
9306 None,
9307 ));
9308 }
9309 }
9310 if let Some(max) = maximum {
9311 let max_sem = semantics::time_to_semantic(max);
9312 if semantics::compare_semantic_times(def, &max_sem) == Ordering::Greater {
9313 errors.push(Error::validation_with_context(
9314 format!(
9315 "Type '{}' default time {} is after maximum {}",
9316 type_name, def, max
9317 ),
9318 Some(source.clone()),
9319 None::<String>,
9320 spec_context.clone(),
9321 None,
9322 ));
9323 }
9324 }
9325 }
9326 }
9327
9328 TypeSpecification::NumberRange { .. }
9329 | TypeSpecification::DateRange { .. }
9330 | TypeSpecification::QuantityRange { .. }
9331 | TypeSpecification::RatioRange { .. }
9332 | TypeSpecification::CalendarRange { .. }
9333 | TypeSpecification::Boolean { .. }
9334 | TypeSpecification::Calendar { .. } => {
9335 }
9337 TypeSpecification::Veto { .. } => {
9338 }
9341 TypeSpecification::Undetermined => unreachable!(
9342 "BUG: validate_type_specification_constraints called with Undetermined sentinel type; this type exists only during type inference"
9343 ),
9344 }
9345
9346 errors
9347}
9348
9349#[cfg(test)]
9350mod validation_tests {
9351 use super::*;
9352 use crate::computation::rational::RationalInteger;
9353 use crate::parsing::ast::{CommandArg, TypeConstraintCommand};
9354 use crate::planning::semantics::TypeSpecification;
9355 use rust_decimal::Decimal;
9356
9357 fn test_source() -> Source {
9358 Source::new(
9359 crate::parsing::source::SourceType::Volatile,
9360 crate::parsing::ast::Span {
9361 start: 0,
9362 end: 0,
9363 line: 1,
9364 col: 0,
9365 },
9366 )
9367 }
9368
9369 fn apply(
9370 specs: TypeSpecification,
9371 command: TypeConstraintCommand,
9372 args: &[CommandArg],
9373 ) -> TypeSpecification {
9374 let mut default = None;
9375 specs
9376 .apply_constraint("test", command, args, &mut default)
9377 .unwrap()
9378 }
9379
9380 fn number_arg(n: i64) -> CommandArg {
9381 CommandArg::Literal(crate::literals::Value::Number(Decimal::from(n)))
9382 }
9383
9384 fn date_arg(s: &str) -> CommandArg {
9385 let dt = s.parse::<crate::literals::DateTimeValue>().expect("date");
9386 CommandArg::Literal(crate::literals::Value::Date(dt))
9387 }
9388
9389 fn time_arg(s: &str) -> CommandArg {
9390 let t = s.parse::<crate::literals::TimeValue>().expect("time");
9391 CommandArg::Literal(crate::literals::Value::Time(t))
9392 }
9393
9394 #[test]
9395 fn validate_number_minimum_greater_than_maximum() {
9396 let mut specs = TypeSpecification::number();
9397 specs = apply(specs, TypeConstraintCommand::Minimum, &[number_arg(100)]);
9398 specs = apply(specs, TypeConstraintCommand::Maximum, &[number_arg(50)]);
9399
9400 let src = test_source();
9401 let errors = validate_type_specifications(&specs, None, "test", &src, None);
9402 assert_eq!(errors.len(), 1);
9403 assert!(errors[0]
9404 .to_string()
9405 .contains("minimum 100 is greater than maximum 50"));
9406 }
9407
9408 #[test]
9409 fn validate_number_default_below_minimum() {
9410 let specs = TypeSpecification::Number {
9411 minimum: Some(RationalInteger::new(10, 1)),
9412 maximum: None,
9413 decimals: None,
9414 help: String::new(),
9415 };
9416 let default = ValueKind::Number(RationalInteger::new(5, 1));
9417
9418 let src = test_source();
9419 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
9420 assert_eq!(errors.len(), 1);
9421 assert!(errors[0]
9422 .to_string()
9423 .contains("default value 5 is less than minimum 10"));
9424 }
9425
9426 #[test]
9427 fn validate_number_default_above_maximum() {
9428 let specs = TypeSpecification::Number {
9429 minimum: None,
9430 maximum: Some(RationalInteger::new(100, 1)),
9431 decimals: None,
9432 help: String::new(),
9433 };
9434 let default = ValueKind::Number(RationalInteger::new(150, 1));
9435
9436 let src = test_source();
9437 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
9438 assert_eq!(errors.len(), 1);
9439 assert!(errors[0]
9440 .to_string()
9441 .contains("default value 150 is greater than maximum 100"));
9442 }
9443
9444 #[test]
9445 fn validate_number_default_valid() {
9446 let specs = TypeSpecification::Number {
9447 minimum: Some(RationalInteger::new(0, 1)),
9448 maximum: Some(RationalInteger::new(100, 1)),
9449 decimals: None,
9450 help: String::new(),
9451 };
9452 let default = ValueKind::Number(RationalInteger::new(50, 1));
9453
9454 let src = test_source();
9455 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
9456 assert!(errors.is_empty());
9457 }
9458
9459 #[test]
9460 fn text_minimum_command_is_rejected() {
9461 let specs = TypeSpecification::text();
9462 let res = specs.apply_constraint(
9463 "test",
9464 TypeConstraintCommand::Minimum,
9465 &[number_arg(5)],
9466 &mut None,
9467 );
9468 assert!(res.is_err());
9469 assert!(res
9470 .unwrap_err()
9471 .contains("Invalid command 'minimum' for text type"));
9472 }
9473
9474 #[test]
9475 fn text_maximum_command_is_rejected() {
9476 let specs = TypeSpecification::text();
9477 let res = specs.apply_constraint(
9478 "test",
9479 TypeConstraintCommand::Maximum,
9480 &[number_arg(5)],
9481 &mut None,
9482 );
9483 assert!(res.is_err());
9484 assert!(res
9485 .unwrap_err()
9486 .contains("Invalid command 'maximum' for text type"));
9487 }
9488
9489 #[test]
9490 fn validate_text_default_not_in_options() {
9491 let specs = TypeSpecification::Text {
9492 length: None,
9493 options: vec!["red".to_string(), "blue".to_string()],
9494 help: String::new(),
9495 };
9496 let default = ValueKind::Text("green".to_string());
9497
9498 let src = test_source();
9499 let errors = validate_type_specifications(&specs, Some(&default), "test", &src, None);
9500 assert_eq!(errors.len(), 1);
9501 assert!(errors[0]
9502 .to_string()
9503 .contains("default value 'green' is not in allowed options"));
9504 }
9505
9506 #[test]
9507 fn validate_ratio_minimum_greater_than_maximum() {
9508 let specs = TypeSpecification::Ratio {
9509 minimum: Some(RationalInteger::new(2, 1)),
9510 maximum: Some(RationalInteger::new(1, 1)),
9511 decimals: None,
9512 units: crate::planning::semantics::RatioUnits::new(),
9513 help: String::new(),
9514 };
9515
9516 let src = test_source();
9517 let errors = validate_type_specifications(&specs, None, "test", &src, None);
9518 assert_eq!(errors.len(), 1);
9519 assert!(errors[0]
9520 .to_string()
9521 .contains("minimum 2 is greater than maximum 1"));
9522 }
9523
9524 #[test]
9525 fn validate_date_minimum_after_maximum() {
9526 let mut specs = TypeSpecification::date();
9527 specs = apply(
9528 specs,
9529 TypeConstraintCommand::Minimum,
9530 &[date_arg("2024-12-31")],
9531 );
9532 specs = apply(
9533 specs,
9534 TypeConstraintCommand::Maximum,
9535 &[date_arg("2024-01-01")],
9536 );
9537
9538 let src = test_source();
9539 let errors = validate_type_specifications(&specs, None, "test", &src, None);
9540 assert_eq!(errors.len(), 1);
9541 assert!(
9542 errors[0].to_string().contains("minimum")
9543 && errors[0].to_string().contains("is after maximum")
9544 );
9545 }
9546
9547 #[test]
9548 fn validate_date_valid_range() {
9549 let mut specs = TypeSpecification::date();
9550 specs = apply(
9551 specs,
9552 TypeConstraintCommand::Minimum,
9553 &[date_arg("2024-01-01")],
9554 );
9555 specs = apply(
9556 specs,
9557 TypeConstraintCommand::Maximum,
9558 &[date_arg("2024-12-31")],
9559 );
9560
9561 let src = test_source();
9562 let errors = validate_type_specifications(&specs, None, "test", &src, None);
9563 assert!(errors.is_empty());
9564 }
9565
9566 #[test]
9567 fn validate_time_minimum_after_maximum() {
9568 let mut specs = TypeSpecification::time();
9569 specs = apply(
9570 specs,
9571 TypeConstraintCommand::Minimum,
9572 &[time_arg("23:00:00")],
9573 );
9574 specs = apply(
9575 specs,
9576 TypeConstraintCommand::Maximum,
9577 &[time_arg("10:00:00")],
9578 );
9579
9580 let src = test_source();
9581 let errors = validate_type_specifications(&specs, None, "test", &src, None);
9582 assert_eq!(errors.len(), 1);
9583 assert!(
9584 errors[0].to_string().contains("minimum")
9585 && errors[0].to_string().contains("is after maximum")
9586 );
9587 }
9588}