1use crate::computation::rational::{rational_is_zero, NumericFailure, RationalInteger};
2use crate::computation::{mathematical_computation_applies_to_magnitude, OperationResult};
3use crate::engine::Context;
4use crate::literals::{MeasureUnit, MeasureUnits};
5use crate::parsing::arena::{ExprId, ExprKind, ExprRef, ExprTable, ExprTree};
6use crate::parsing::ast::{
7 self as ast, CommandArg, Constraint, ConversionTypeKind, EffectiveDate, LemmaData,
8 LemmaRepository, LemmaRule, LemmaSpec, ParentType, PrimitiveKind, TypeConstraintCommand, Value,
9 WithRhs,
10};
11use crate::parsing::source::Source;
12use crate::planning::discovery;
13use crate::planning::execution_plan::{self, ExecutionPlan};
14use crate::planning::instance_list::{
15 self, InstanceListSource, InstanceLists, KeyIndex, RowHop, RowStore, RowTemplate, StoreKeyField,
16};
17use crate::planning::normalize::{LeafKind, NormalFormId, NormalFormKind, NormalFormListOp};
18use crate::planning::semantics::{
19 self, apply_list_with_rows, apply_option_constraint, apply_spec_member_command,
20 bound_value_kind_from_raw_suggestion, calendar_decomposition, calendar_unit_factor,
21 canonicalize_signature, conversion_target_to_semantic, duration_decomposition,
22 lift_range_endpoint, number_with_unit_to_value_kind, parser_value_to_value_kind,
23 primitive_boolean_arc, primitive_date_arc, primitive_number_arc, primitive_ratio_arc,
24 primitive_text_arc, primitive_time_arc, range_type_specification_from_endpoints,
25 scale_decomposition, value_kind_matches_spec, value_to_semantic, ArithmeticComputation,
26 BaseMeasureVector, BoundValueKind, ComparisonComputation, ConstraintNames, DataDefinition,
27 DataOrigin, DataPath, Expression, ExpressionKind, ImportEffectivePin, KeyField, LemmaType,
28 ListKey, ListOp, ListPredKind, ListReduce, LiteralValue, PathSegment, RawSuggestion,
29 ReferenceEnd, ReferenceTarget, RowBinding, RowEnd, RowInstance, RowOrigin, RulePath,
30 SemanticConversionTarget, SpecIdentity, SpecInstance, SpecVersionKey, TypeDefiningSpec,
31 TypeExtends, TypeFamily, TypeOptions, TypeSpecification, TypedLiteral, UnitTokenResolver,
32 ValueKind, SPEC_LIST_HAS_NO_OPTIONS,
33};
34use crate::planning::spec_value::{RowFieldRead, RulePresence, SpecCandidates};
35use crate::planning::typing::{
36 comparison_type, date_predicate_type, list_command_result_type, list_has_instance_rows,
37 logical_connective_type, logical_not_type, math_op_type, measure_range_matches_measure,
38 past_future_range_type, piecewise_type, range_bound_result_type, range_matches_measure_type,
39 range_matches_range_measure, range_span_type, refine_list_key_type, resolve_anonymous_measure,
40 result_is_veto_type, step_rounding_compatible, step_rounding_type, unit_conversion_type,
41 ListCommandResultKind, MeasureScope,
42};
43use crate::planning::unit_index::{
44 PlanningOwner, PlanningUnitIndex, UnitIndex, UnitMergeConflict, UnitOwner,
45};
46use crate::Error;
47
48use crate::computation::arithmetic::{SignatureFactor, SignatureIndex};
49use crate::computation::units::owning_type_declares_unit_name;
50use crate::evaluation::evaluate_data::typed_literal_from_parsed;
51use crate::limits::{ResourceLimits, MAX_UNIT_EXPONENT};
52pub use crate::planning::typing::DecompositionMatch;
53pub(crate) use crate::planning::typing::{
54 compute_arithmetic_result_type, find_unique_measure_type_in_unit_index,
55 infer_range_type_from_endpoint_types, quantity_integer_power_type,
56};
57use crate::string_distance::closest_name;
58use ast::DataValue as ParsedDataValue;
59use indexmap::IndexMap;
60use rust_decimal::Decimal;
61use std::cmp::Ordering;
62use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
63use std::fmt;
64use std::sync::Arc;
65
66enum PlannedImport {
68 Static {
69 identity: Arc<SpecIdentity>,
70 template: Arc<ExecutionPlan>,
71 },
72 Dynamic {
73 pin: ImportEffectivePin,
74 candidates: Vec<(Arc<SpecIdentity>, Arc<ExecutionPlan>)>,
75 },
76}
77
78impl PlannedImport {
79 fn primary_identity(&self) -> &Arc<SpecIdentity> {
80 match self {
81 PlannedImport::Static { identity, .. } => identity,
82 PlannedImport::Dynamic { candidates, .. } => {
83 &candidates
84 .first()
85 .expect("BUG: dynamic import candidates are non-empty")
86 .0
87 }
88 }
89 }
90
91 fn is_dynamic(&self) -> bool {
92 matches!(self, PlannedImport::Dynamic { .. })
93 }
94}
95
96#[derive(Default)]
102struct InferExpressionTypeCache {
103 tables: HashMap<usize, Vec<Arc<LemmaType>>>,
106 typed: Vec<usize>,
108}
109
110impl InferExpressionTypeCache {
111 fn new() -> Self {
112 Self::default()
113 }
114
115 fn clear(&mut self) {
117 for key in self.typed.drain(..) {
118 self.tables
119 .get_mut(&key)
120 .expect("BUG: a typed table is in the cache")
121 .clear();
122 }
123 }
124}
125
126type DataBindings = IndexMap<Vec<String>, (BindingValue, Source)>;
133
134#[derive(Debug, Clone)]
143pub(crate) enum BindingValue {
144 Literal(ast::Value),
146 Reference { target: ReferenceTarget },
148 InstanceAliases(Vec<ast::Reference>),
151 InstanceRows(Vec<RowCopy>),
153}
154
155#[derive(Debug, Clone)]
160pub(crate) struct RowCopy {
161 identity: SpecIdentity,
163 name: String,
165 bindings: DataBindings,
166 source: Source,
167}
168
169type RowKey = *const RowInstance;
171
172#[derive(Clone)]
176struct Located<'s> {
177 row: &'s RowInstance,
178 context: Vec<&'s RowInstance>,
179}
180
181impl<'s> Located<'s> {
182 fn root(row: &'s RowInstance) -> Self {
183 Self {
184 row,
185 context: Vec::new(),
186 }
187 }
188
189 fn inside(&self) -> Vec<&'s RowInstance> {
191 let mut context = self.context.clone();
192 context.push(self.row);
193 context
194 }
195
196 fn context_at(&self, hops: u32) -> Vec<&'s RowInstance> {
199 let hops = usize::try_from(hops).expect("BUG: hops fit usize");
200 let len = self.context.len();
201 assert!(
202 hops <= len,
203 "BUG: a binding hops above the plan being built"
204 );
205 self.context[..len - hops].to_vec()
206 }
207
208 fn scope(&self, hops: u32) -> Option<Located<'s>> {
211 let mut context = self.context_at(hops);
212 let row = context.pop()?;
213 Some(Located { row, context })
214 }
215
216 fn key(&self) -> Vec<RowKey> {
217 self.context
218 .iter()
219 .chain(std::iter::once(&self.row))
220 .map(|row| *row as RowKey)
221 .collect()
222 }
223
224 fn import_segments(&self) -> Option<Vec<PathSegment>> {
227 self.context
228 .iter()
229 .chain(std::iter::once(&self.row))
230 .map(|row| match &row.origin {
231 RowOrigin::Import { alias } => Some(PathSegment::import(alias, &row.identity)),
232 RowOrigin::Numbered { .. }
233 | RowOrigin::Written { .. }
234 | RowOrigin::Inline { .. } => None,
235 })
236 .collect()
237 }
238}
239
240struct ForcedRead<'s> {
242 at: Located<'s>,
243 end: RowEnd,
244}
245
246#[derive(Clone, Copy, PartialEq, Eq)]
248enum RuleWalk {
249 Follow,
251 Show,
253}
254
255enum Landing<'s> {
257 Plan(RowEnd),
258 Row(ForcedRead<'s>),
259}
260
261#[derive(Debug, Default)]
264pub(crate) struct ShowImports {
265 pub(crate) edges: BTreeMap<RulePath, BTreeSet<RulePath>>,
268 pub(crate) need_edges: BTreeMap<RulePath, BTreeSet<RulePath>>,
271 pub(crate) data_reads: BTreeMap<RulePath, BTreeSet<DataPath>>,
274 pub(crate) data: BTreeMap<DataPath, SpecIdentity>,
276 pub(crate) rules: BTreeMap<RulePath, (SpecIdentity, RulePath)>,
278}
279
280type ScopeKey = Vec<Vec<Vec<RowKey>>>;
281
282fn scope_key(scope: &[Vec<Located<'_>>]) -> ScopeKey {
283 scope
284 .iter()
285 .map(|rows| rows.iter().map(Located::key).collect())
286 .collect()
287}
288
289fn level_rows<'r, 's>(scope: &'r [Vec<Located<'s>>], level: u32) -> &'r [Located<'s>] {
291 scope
292 .get(usize::try_from(level).expect("BUG: row level fits usize"))
293 .expect("BUG: row read outside its list command's scope")
294}
295
296#[derive(Debug)]
297pub(crate) struct Graph<'a> {
298 main_spec: &'a LemmaSpec,
300 data: IndexMap<DataPath, DataDefinition>,
301 rules: BTreeMap<RulePath, RuleNode<'a>>,
302 dropped_rules: BTreeSet<RulePath>,
304 rule_order: Vec<RulePath>,
306 data_reference_order: Vec<DataPath>,
310 reference_ends: IndexMap<DataPath, ReferenceEnd>,
313 rule_candidates: HashMap<RulePath, SpecCandidates>,
316 instance_lists: InstanceLists,
317 show_imports: ShowImports,
320 show_edges: BTreeMap<RulePath, BTreeSet<RulePath>>,
322 show_order: Vec<RulePath>,
324 unit_scale_targets: IndexMap<(TypeFamily, String), ReferenceTarget>,
328}
329
330impl<'a> Graph<'a> {
331 pub(crate) fn unit_scale_targets(&self) -> &IndexMap<(TypeFamily, String), ReferenceTarget> {
332 &self.unit_scale_targets
333 }
334
335 pub(crate) fn data(&self) -> &IndexMap<DataPath, DataDefinition> {
336 &self.data
337 }
338
339 pub(crate) fn rules(&self) -> &BTreeMap<RulePath, RuleNode<'a>> {
340 &self.rules
341 }
342
343 pub(crate) fn rule_presence(&self, path: &RulePath) -> RulePresence {
344 if self.rules.contains_key(path) {
345 RulePresence::Present
346 } else if self.dropped_rules.contains(path) {
347 RulePresence::Dropped
348 } else {
349 RulePresence::Absent
350 }
351 }
352
353 pub(crate) fn rules_mut(&mut self) -> &mut BTreeMap<RulePath, RuleNode<'a>> {
354 &mut self.rules
355 }
356
357 pub(crate) fn rule_order(&self) -> &[RulePath] {
358 &self.rule_order
359 }
360
361 pub(crate) fn data_reference_order(&self) -> &[DataPath] {
362 &self.data_reference_order
363 }
364
365 pub(crate) fn reference_ends(&self) -> &IndexMap<DataPath, ReferenceEnd> {
366 &self.reference_ends
367 }
368
369 pub(crate) fn rule_candidates(&self) -> &HashMap<RulePath, SpecCandidates> {
370 &self.rule_candidates
371 }
372
373 pub(crate) fn show_imports(&self) -> &ShowImports {
374 &self.show_imports
375 }
376
377 pub(crate) fn show_edges(&self) -> &BTreeMap<RulePath, BTreeSet<RulePath>> {
378 &self.show_edges
379 }
380
381 pub(crate) fn show_order(&self) -> &[RulePath] {
382 &self.show_order
383 }
384
385 pub(crate) fn main_spec(&self) -> &LemmaSpec {
386 self.main_spec
387 }
388
389 pub(crate) fn instance_lists(&self) -> &InstanceLists {
390 &self.instance_lists
391 }
392
393 pub(crate) fn build_data(
396 &self,
397 resolved_by_type_name: &IndexMap<String, Arc<LemmaType>>,
398 ) -> Result<IndexMap<DataPath, DataDefinition>, Vec<Error>> {
399 struct PendingReference {
400 target: ReferenceTarget,
401 resolved_type: Arc<LemmaType>,
402 local_constraints: Option<Vec<Constraint>>,
403 local_suggestion: Option<BoundValueKind>,
404 local_value: Option<BoundValueKind>,
405 origin: DataOrigin,
406 }
407
408 let mut schema: HashMap<DataPath, Arc<LemmaType>> = HashMap::new();
409 let mut declared_suggestions: HashMap<DataPath, BoundValueKind> = HashMap::new();
410 let mut declared_values: HashMap<DataPath, BoundValueKind> = HashMap::new();
411 let mut values: HashMap<DataPath, (LiteralValue, DataOrigin)> = HashMap::new();
412 let mut value_sources: HashMap<DataPath, Source> = HashMap::new();
413 let mut references: HashMap<DataPath, PendingReference> = HashMap::new();
414
415 for (path, rfv) in self.data.iter() {
416 match rfv {
417 DataDefinition::Value {
418 value,
419 resolved_type,
420 origin,
421 source,
422 } => {
423 values.insert(path.clone(), (value.clone(), *origin));
424 value_sources.insert(path.clone(), source.clone());
425 schema.insert(path.clone(), Arc::clone(resolved_type));
426 }
427 DataDefinition::TypeDeclaration {
428 resolved_type,
429 declared_suggestion,
430 declared_value,
431 ..
432 } => {
433 schema.insert(path.clone(), Arc::clone(resolved_type));
434 if let Some(dv) = declared_suggestion {
435 declared_suggestions.insert(path.clone(), dv.clone());
436 }
437 if let Some(dv) = declared_value {
438 declared_values.insert(path.clone(), dv.clone());
439 }
440 }
441 DataDefinition::Import { .. } => {}
442 DataDefinition::InstanceList { .. } => {}
443 DataDefinition::Reference {
444 target,
445 resolved_type,
446 local_constraints,
447 local_suggestion,
448 local_value,
449 origin,
450 ..
451 } => {
452 schema.insert(path.clone(), Arc::clone(resolved_type));
453 references.insert(
454 path.clone(),
455 PendingReference {
456 target: target.clone(),
457 resolved_type: Arc::clone(resolved_type),
458 local_constraints: local_constraints.clone(),
459 local_suggestion: local_suggestion.clone(),
460 local_value: local_value.clone(),
461 origin: *origin,
462 },
463 );
464 }
465 }
466 }
467
468 let mut coercion_errors: Vec<Error> = Vec::new();
469 for (path, (value, _)) in values.iter_mut() {
470 let Some(schema_type) = schema.get(path).cloned() else {
471 continue;
472 };
473 if let Some(type_name) = schema_type.name.as_deref() {
474 if let Some(resolved) = resolved_by_type_name.get(type_name) {
475 if let Err(failure) = semantics::refresh_measure_literal_canonical_magnitude(
476 value,
477 schema_type.as_ref(),
478 resolved,
479 ) {
480 coercion_errors.push(Error::validation(
481 format!("Data '{path}' measure recanonicalization failed: {failure}"),
482 value_sources.get(path).cloned(),
483 None::<String>,
484 ));
485 continue;
486 }
487 }
488 }
489 let typed = TypedLiteral {
490 value: value.value.clone(),
491 lemma_type: Arc::clone(&schema_type),
492 };
493 match Self::coerce_literal_to_schema_type(&typed, &schema_type) {
494 Ok(coerced) => {
495 *value = coerced.to_literal();
496 schema.insert(path.clone(), Arc::clone(&coerced.lemma_type));
497 }
498 Err(msg) => {
499 coercion_errors.push(Error::validation(
500 format!("Data '{path}' incompatible with declared type: {msg}"),
501 value_sources.get(path).cloned(),
502 None::<String>,
503 ));
504 }
505 }
506 }
507 if !coercion_errors.is_empty() {
508 return Err(coercion_errors);
509 }
510
511 let mut data = IndexMap::new();
512 for (path, rfv) in &self.data {
513 let source = rfv.source().clone();
514 if let DataDefinition::InstanceList { .. } | DataDefinition::Import { .. } = rfv {
515 data.insert(path.clone(), rfv.clone());
516 } else if let Some(pending) = references.remove(path) {
517 data.insert(
518 path.clone(),
519 DataDefinition::Reference {
520 target: pending.target,
521 resolved_type: pending.resolved_type,
522 local_constraints: pending.local_constraints,
523 local_suggestion: pending.local_suggestion,
524 local_value: pending.local_value,
525 origin: pending.origin,
526 source,
527 },
528 );
529 } else if let Some((value, origin)) = values.remove(path) {
530 let resolved_type = schema
531 .remove(path)
532 .expect("BUG: value data must have schema type after coercion");
533 data.insert(
534 path.clone(),
535 DataDefinition::Value {
536 value,
537 resolved_type,
538 origin,
539 source,
540 },
541 );
542 } else {
543 let resolved_type = schema
544 .get(path)
545 .cloned()
546 .expect("non-spec-ref data has schema (value, reference, or type-only)");
547 let declared_suggestion = declared_suggestions.remove(path);
548 let declared_value = declared_values.remove(path);
549 data.insert(
550 path.clone(),
551 DataDefinition::TypeDeclaration {
552 resolved_type,
553 declared_suggestion,
554 declared_value,
555 source,
556 },
557 );
558 }
559 }
560 Ok(data)
561 }
562
563 fn preserve_measure_binding(
566 schema_type: &Arc<LemmaType>,
567 previous: &LemmaType,
568 ) -> Arc<LemmaType> {
569 match previous.unit() {
570 Some(unit)
571 if matches!(
572 &schema_type.specifications,
573 TypeSpecification::Measure { .. } | TypeSpecification::Ratio { .. }
574 ) =>
575 {
576 Arc::new(schema_type.as_ref().clone().with_unit(unit))
577 }
578 Some(_) | None => Arc::clone(schema_type),
579 }
580 }
581
582 pub(crate) fn coerce_literal_to_schema_type(
583 lit: &TypedLiteral,
584 schema_type: &Arc<LemmaType>,
585 ) -> Result<TypedLiteral, String> {
586 fn range_endpoint_schema_type(schema_type: &LemmaType) -> Option<Arc<LemmaType>> {
587 schema_type
588 .specifications
589 .element_from_range()
590 .map(|element_spec| Arc::new(LemmaType::primitive(element_spec)))
591 }
592
593 let schema_ref = schema_type.as_ref();
594 match (&schema_ref.specifications, &lit.value) {
595 (
596 TypeSpecification::NumberRange { .. }
597 | TypeSpecification::DateRange { .. }
598 | TypeSpecification::TimeRange { .. }
599 | TypeSpecification::RatioRange { .. }
600 | TypeSpecification::MeasureRange { .. },
601 ValueKind::Range(left, right),
602 ) if !lit
603 .lemma_type
604 .specifications
605 .equal_ignoring_unit(&schema_ref.specifications) =>
606 {
607 let endpoint_schema_type =
608 range_endpoint_schema_type(schema_ref).unwrap_or_else(|| {
609 unreachable!("BUG: range_endpoint_schema_type missing range schema arm")
610 });
611 let left_schema =
612 Self::preserve_measure_binding(&endpoint_schema_type, left.lemma_type.as_ref());
613 let right_schema = Self::preserve_measure_binding(
614 &endpoint_schema_type,
615 right.lemma_type.as_ref(),
616 );
617 let coerced_left = Self::coerce_scalar_literal(left, &left_schema)?;
618 let coerced_right = Self::coerce_scalar_literal(right, &right_schema)?;
619 Ok(TypedLiteral {
620 value: ValueKind::Range(Box::new(coerced_left), Box::new(coerced_right)),
621 lemma_type: Arc::clone(schema_type),
622 })
623 }
624 _ => Self::coerce_scalar_literal(lit, schema_type),
625 }
626 }
627
628 fn coerce_scalar_literal(
631 lit: &TypedLiteral,
632 schema_type: &Arc<LemmaType>,
633 ) -> Result<TypedLiteral, String> {
634 let schema_ref = schema_type.as_ref();
635 if lit
636 .lemma_type
637 .specifications
638 .equal_ignoring_unit(&schema_ref.specifications)
639 {
640 if !value_kind_matches_spec(&lit.value, &schema_ref.specifications) {
641 panic!(
642 "BUG: LiteralValue value kind {:?} inconsistent with lemma_type {:?}",
643 lit.value, lit.lemma_type.specifications
644 );
645 }
646 let mut out = lit.clone();
647 out.lemma_type = Self::preserve_measure_binding(schema_type, &lit.lemma_type);
648 return Ok(out);
649 }
650 match (&schema_ref.specifications, &lit.value) {
651 (TypeSpecification::Number { .. }, ValueKind::Number(_))
652 | (TypeSpecification::Text { .. }, ValueKind::Text(_))
653 | (TypeSpecification::Boolean { .. }, ValueKind::Boolean(_))
654 | (TypeSpecification::Date { .. }, ValueKind::Date(_))
655 | (TypeSpecification::Time { .. }, ValueKind::Time(_)) => {
656 let mut out = lit.clone();
657 out.lemma_type = Arc::clone(schema_type);
658 Ok(out)
659 }
660 (TypeSpecification::Measure { .. }, ValueKind::Measure(_)) => {
661 if !lit.lemma_type.same_measure_family(schema_ref)
662 && !lit.lemma_type.compatible_with_anonymous_measure(schema_ref)
663 && !lit
664 .lemma_type
665 .specifications
666 .equal_ignoring_unit(&schema_ref.specifications)
667 {
668 return Err(format!(
669 "value {} cannot be used as type {}: incompatible measure families",
670 lit,
671 schema_ref.name()
672 ));
673 }
674 let mut out = lit.clone();
675 out.lemma_type = Self::preserve_measure_binding(schema_type, &lit.lemma_type);
676 Ok(out)
677 }
678 (TypeSpecification::Ratio { .. }, ValueKind::Ratio(_)) => {
679 let mut out = lit.clone();
680 out.lemma_type = Self::preserve_measure_binding(schema_type, &lit.lemma_type);
681 Ok(out)
682 }
683 _ => Err(format!(
684 "value {} cannot be used as type {}",
685 lit,
686 schema_ref.name()
687 )),
688 }
689 }
690
691 fn resolve_data_reference_types(
709 &mut self,
710 ordered_reference_paths: &[DataPath],
711 resolved_types: &ResolvedTypesMap,
712 ) -> Result<(), Vec<Error>> {
713 let mut errors: Vec<Error> = Vec::new();
714 let main_spec = self.main_spec;
715 let resolve_unit = unit_tokens_for_spec(resolved_types, main_spec);
716
717 for reference_path in ordered_reference_paths {
718 let (target_data_path, provisional, local_constraints, source) =
719 match self.data.get(reference_path) {
720 Some(DataDefinition::Reference {
721 target: ReferenceTarget::Data(path),
722 resolved_type,
723 local_constraints,
724 source,
725 ..
726 }) => (
727 path.clone(),
728 Arc::clone(resolved_type),
729 local_constraints.clone(),
730 source.clone(),
731 ),
732 _ => unreachable!(
733 "BUG: reference evaluation order must contain only data-target references"
734 ),
735 };
736
737 let Some(target_entry) = self.data.get(&target_data_path) else {
738 errors.push(reference_error(
739 self.main_spec,
740 &source,
741 format!(
742 "Data reference '{}' target '{}' does not exist",
743 reference_path, target_data_path
744 ),
745 ));
746 continue;
747 };
748
749 let target_type_arc = match target_entry {
750 DataDefinition::TypeDeclaration { resolved_type, .. }
751 | DataDefinition::Reference { resolved_type, .. }
752 | DataDefinition::InstanceList { resolved_type, .. }
753 | DataDefinition::Value { resolved_type, .. } => Arc::clone(resolved_type),
754 DataDefinition::Import { .. } => {
755 errors.push(reference_error(
756 self.main_spec,
757 &source,
758 format!(
759 "Data reference '{}' target '{}' is a spec reference and cannot carry a value",
760 reference_path, target_data_path
761 ),
762 ));
763 continue;
764 }
765 };
766
767 let lhs_declared_type: Option<&LemmaType> = if provisional.is_undetermined() {
768 None
769 } else {
770 Some(provisional.as_ref())
771 };
772
773 if let Some(lhs) = lhs_declared_type {
774 if let Some(msg) = reference_kind_mismatch_message(
775 lhs,
776 target_type_arc.as_ref(),
777 reference_path,
778 &target_data_path,
779 "target",
780 ) {
781 errors.push(reference_error(self.main_spec, &source, msg));
782 continue;
783 }
784 }
785
786 let mut merged = match lhs_declared_type {
791 Some(lhs) => lhs.clone(),
792 None => target_type_arc.as_ref().clone(),
793 };
794 let mut raw_suggestion: Option<RawSuggestion> = None;
795 let mut raw_value: Option<RawSuggestion> = None;
796 if let Some(constraints) = &local_constraints {
797 let constraint_type_name = merged.name();
798 match apply_constraints_to_spec(
799 self.main_spec,
800 &constraint_type_name,
801 &reference_path.data,
802 &mut merged,
803 constraints,
804 &source,
805 ConstraintSuggestionSink {
806 suggestion: &mut raw_suggestion,
807 value: &mut raw_value,
808 resolve_unit: Some(&resolve_unit),
809 },
810 ) {
811 Ok(()) => {}
812 Err(errs) => {
813 errors.extend(errs);
814 continue;
815 }
816 }
817 }
818 let captured_suggestion = match raw_suggestion {
819 None => None,
820 Some(raw) => {
821 match bound_value_kind_from_raw_suggestion(
822 raw,
823 &merged.specifications,
824 &merged.name(),
825 ) {
826 Ok(vk) => Some(vk),
827 Err(message) => {
828 errors.push(reference_error(self.main_spec, &source, message));
829 continue;
830 }
831 }
832 }
833 };
834 let captured_value = match raw_value {
835 None => None,
836 Some(raw) => {
837 match bound_value_kind_from_raw_suggestion(
838 raw,
839 &merged.specifications,
840 &merged.name(),
841 ) {
842 Ok(vk) => Some(vk),
843 Err(message) => {
844 errors.push(reference_error(self.main_spec, &source, message));
845 continue;
846 }
847 }
848 }
849 };
850
851 if let Some(DataDefinition::Reference {
854 resolved_type,
855 local_suggestion,
856 local_value,
857 ..
858 }) = self.data.get_mut(reference_path)
859 {
860 *resolved_type = Arc::new(merged);
861 if captured_suggestion.is_some() {
862 *local_suggestion = captured_suggestion;
863 }
864 if captured_value.is_some() {
865 *local_value = captured_value;
866 }
867 } else {
868 unreachable!("BUG: reference path disappeared during type resolution");
869 }
870 }
871
872 if errors.is_empty() {
873 Ok(())
874 } else {
875 Err(errors)
876 }
877 }
878
879 fn resolve_rule_reference_types(
888 &mut self,
889 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
890 resolved_types: &ResolvedTypesMap,
891 ) -> Result<(), Vec<Error>> {
892 let mut errors: Vec<Error> = Vec::new();
893 let mut updates: Vec<RuleReferenceUpdate> = Vec::new();
894 let main_spec = self.main_spec;
895 let resolve_unit = unit_tokens_for_spec(resolved_types, main_spec);
896
897 for (reference_path, entry) in &self.data {
898 let DataDefinition::Reference {
899 target,
900 resolved_type: provisional,
901 local_constraints,
902 source,
903 ..
904 } = entry
905 else {
906 continue;
907 };
908
909 let target_rule_path = match target {
910 ReferenceTarget::Rule(path) => path,
911 ReferenceTarget::Data(_) => continue,
912 };
913
914 let Some(target_type) = computed_rule_types.get(target_rule_path) else {
915 match self.rule_presence(target_rule_path) {
916 RulePresence::Dropped => {}
917 RulePresence::Present => {
918 panic!(
919 "BUG: rule '{target_rule_path}' type not inferred before dependent '{reference_path}' in topological order"
920 )
921 }
922 RulePresence::Absent => {
923 errors.push(reference_error(
924 self.main_spec,
925 source,
926 format!(
927 "Data reference '{}' target rule '{}' does not exist",
928 reference_path, target_rule_path
929 ),
930 ));
931 }
932 }
933 continue;
934 };
935
936 if target_type.is_spec() {
937 errors.push(reference_error(
938 self.main_spec,
939 source,
940 format!(
941 "cannot supply spec instance '{}' as a value",
942 target_rule_path.rule
943 ),
944 ));
945 continue;
946 }
947
948 if target_type.vetoed() || target_type.is_undetermined() {
953 let mut merged = target_type.as_ref().clone();
954 let mut raw_suggestion: Option<RawSuggestion> = None;
955 let mut raw_value: Option<RawSuggestion> = None;
956 if let Some(constraints) = local_constraints {
957 let constraint_type_name = merged.name();
958 match apply_constraints_to_spec(
959 self.main_spec,
960 &constraint_type_name,
961 &reference_path.data,
962 &mut merged,
963 constraints,
964 source,
965 ConstraintSuggestionSink {
966 suggestion: &mut raw_suggestion,
967 value: &mut raw_value,
968 resolve_unit: Some(&resolve_unit),
969 },
970 ) {
971 Ok(()) => {}
972 Err(errs) => {
973 errors.extend(errs);
974 continue;
975 }
976 }
977 }
978 let captured_suggestion = match raw_suggestion {
979 None => None,
980 Some(raw) => {
981 match bound_value_kind_from_raw_suggestion(
982 raw,
983 &merged.specifications,
984 &merged.name(),
985 ) {
986 Ok(vk) => Some(vk),
987 Err(message) => {
988 errors.push(reference_error(self.main_spec, source, message));
989 continue;
990 }
991 }
992 }
993 };
994 let captured_value = match raw_value {
995 None => None,
996 Some(raw) => {
997 match bound_value_kind_from_raw_suggestion(
998 raw,
999 &merged.specifications,
1000 &merged.name(),
1001 ) {
1002 Ok(vk) => Some(vk),
1003 Err(message) => {
1004 errors.push(reference_error(self.main_spec, source, message));
1005 continue;
1006 }
1007 }
1008 }
1009 };
1010 updates.push((
1011 reference_path.clone(),
1012 Arc::new(merged),
1013 captured_suggestion,
1014 captured_value,
1015 ));
1016 continue;
1017 }
1018
1019 let lhs_declared_type: Option<&LemmaType> = if provisional.is_undetermined() {
1020 None
1021 } else {
1022 Some(provisional.as_ref())
1023 };
1024
1025 if let Some(lhs) = lhs_declared_type {
1026 if let Some(msg) = reference_kind_mismatch_message(
1027 lhs,
1028 target_type,
1029 reference_path,
1030 target_rule_path,
1031 "target rule",
1032 ) {
1033 errors.push(reference_error(self.main_spec, source, msg));
1034 continue;
1035 }
1036 }
1037
1038 let mut merged = match lhs_declared_type {
1041 Some(lhs) => lhs.clone(),
1042 None => target_type.as_ref().clone(),
1043 };
1044 let mut raw_suggestion: Option<RawSuggestion> = None;
1045 let mut raw_value: Option<RawSuggestion> = None;
1046 if let Some(constraints) = local_constraints {
1047 let constraint_type_name = merged.name();
1048 match apply_constraints_to_spec(
1049 self.main_spec,
1050 &constraint_type_name,
1051 &reference_path.data,
1052 &mut merged,
1053 constraints,
1054 source,
1055 ConstraintSuggestionSink {
1056 suggestion: &mut raw_suggestion,
1057 value: &mut raw_value,
1058 resolve_unit: Some(&resolve_unit),
1059 },
1060 ) {
1061 Ok(()) => {}
1062 Err(errs) => {
1063 errors.extend(errs);
1064 continue;
1065 }
1066 }
1067 }
1068 let captured_suggestion = match raw_suggestion {
1069 None => None,
1070 Some(raw) => {
1071 match bound_value_kind_from_raw_suggestion(
1072 raw,
1073 &merged.specifications,
1074 &merged.name(),
1075 ) {
1076 Ok(vk) => Some(vk),
1077 Err(message) => {
1078 errors.push(reference_error(self.main_spec, source, message));
1079 continue;
1080 }
1081 }
1082 }
1083 };
1084 let captured_value = match raw_value {
1085 None => None,
1086 Some(raw) => {
1087 match bound_value_kind_from_raw_suggestion(
1088 raw,
1089 &merged.specifications,
1090 &merged.name(),
1091 ) {
1092 Ok(vk) => Some(vk),
1093 Err(message) => {
1094 errors.push(reference_error(self.main_spec, source, message));
1095 continue;
1096 }
1097 }
1098 }
1099 };
1100
1101 updates.push((
1102 reference_path.clone(),
1103 Arc::new(merged),
1104 captured_suggestion,
1105 captured_value,
1106 ));
1107 }
1108
1109 for (path, new_type, new_suggestion, new_value) in updates {
1110 if let Some(DataDefinition::Reference {
1111 resolved_type,
1112 local_suggestion,
1113 local_value,
1114 ..
1115 }) = self.data.get_mut(&path)
1116 {
1117 *resolved_type = new_type;
1118 if new_suggestion.is_some() {
1119 *local_suggestion = new_suggestion;
1120 }
1121 if new_value.is_some() {
1122 *local_value = new_value;
1123 }
1124 } else {
1125 unreachable!(
1126 "BUG: rule-target reference path disappeared between collect and update phases"
1127 );
1128 }
1129 }
1130
1131 if errors.is_empty() {
1132 Ok(())
1133 } else {
1134 Err(errors)
1135 }
1136 }
1137
1138 fn check_row_store_reference_types(
1148 &mut self,
1149 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
1150 ) -> Result<(), Vec<Error>> {
1151 let mut errors = Vec::new();
1152 let graph = StoredRowKindCheckGraph {
1153 main_spec: self.main_spec,
1154 lists: &self.instance_lists,
1155 data: &self.data,
1156 rules: &self.rules,
1157 dropped_rules: &self.dropped_rules,
1158 computed_rule_types,
1159 };
1160 check_stored_row_reference_types(&graph, &mut errors);
1161 if errors.is_empty() {
1162 Ok(())
1163 } else {
1164 Err(errors)
1165 }
1166 }
1167
1168 fn add_rule_reference_dependency_edges(&mut self) {
1169 let mut reference_targets: BTreeMap<RulePath, BTreeSet<RulePath>> = BTreeMap::new();
1170 for (rule_path, rule_node) in &self.rules {
1171 let mut found = BTreeSet::new();
1172 for node in rule_node.expression.table.nodes() {
1173 if let ExpressionKind::DataPath(path) = &node.kind {
1174 if let Some(ReferenceEnd::Rule(target)) = self.reference_ends.get(path) {
1175 found.insert(target.clone());
1176 }
1177 }
1178 }
1179 reference_targets.insert(rule_path.clone(), found);
1180 }
1181
1182 for (rule_path, targets) in reference_targets {
1183 self.rules
1184 .get_mut(&rule_path)
1185 .expect("BUG: rule with reference targets missing in graph")
1186 .depends_on_rules
1187 .extend(targets);
1188 }
1189 }
1190
1191 fn add_dynamic_import_pin_dependency_edges(&mut self) {
1194 let mut pin_by_alias: HashMap<String, RulePath> = HashMap::new();
1195 for (path, definition) in &self.data {
1196 let DataDefinition::Import {
1197 pin: Some(ImportEffectivePin::Rule(pin)),
1198 ..
1199 } = definition
1200 else {
1201 continue;
1202 };
1203 pin_by_alias.insert(path.data.clone(), pin.clone());
1204 }
1205 if pin_by_alias.is_empty() {
1206 return;
1207 }
1208 let mut updates: Vec<(RulePath, RulePath)> = Vec::new();
1209 for (rule_path, rule_node) in &self.rules {
1210 let mut pins = BTreeSet::new();
1211 for node in rule_node.expression.table.nodes() {
1212 let ExpressionKind::SpecMember { base, .. } = &node.kind else {
1213 continue;
1214 };
1215 let ExpressionKind::DataPath(path) = rule_node.expression.table.kind(*base) else {
1216 continue;
1217 };
1218 if let Some(pin) = pin_by_alias.get(&path.data) {
1219 pins.insert(pin.clone());
1220 }
1221 }
1222 for pin in pins {
1223 updates.push((rule_path.clone(), pin));
1224 }
1225 }
1226 for (rule_path, pin) in updates {
1227 self.rules
1228 .get_mut(&rule_path)
1229 .expect("BUG: rule from dynamic pin edge missing in graph")
1230 .depends_on_rules
1231 .insert(pin);
1232 }
1233 }
1234
1235 fn add_options_rule_dependency_edges(&mut self) {
1238 let mut updates: Vec<(RulePath, RulePath)> = Vec::new();
1239 for (rule_path, rule_node) in &self.rules {
1240 let mut targets = BTreeSet::new();
1241 for node in rule_node.expression.table.nodes() {
1242 let ExpressionKind::DataPath(path) = &node.kind else {
1243 continue;
1244 };
1245 let Some(target) = self
1246 .data
1247 .get(path)
1248 .and_then(|definition| definition.schema_type())
1249 .and_then(|lemma_type| execution_plan::options_rule_path(path, lemma_type))
1250 else {
1251 continue;
1252 };
1253 if self.rules.contains_key(&target) {
1254 targets.insert(target);
1255 }
1256 }
1257 for target in targets {
1258 updates.push((rule_path.clone(), target));
1259 }
1260 }
1261 for (rule_path, target) in updates {
1262 self.rules
1263 .get_mut(&rule_path)
1264 .expect("BUG: rule from iteration missing in graph")
1265 .depends_on_rules
1266 .insert(target);
1267 }
1268 }
1269
1270 fn plan_unit_scales(
1282 &mut self,
1283 resolved_types: &ResolvedTypesMap,
1284 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
1285 cache: &mut InferExpressionTypeCache,
1286 ) -> Vec<Error> {
1287 let Some(types) = find_types_by_spec(resolved_types, self.main_spec) else {
1289 return Vec::new();
1290 };
1291 self.unit_scale_targets = collect_unit_scale_targets(types, &self.data);
1292
1293 let mut scale_rules: BTreeMap<TypeFamily, BTreeSet<RulePath>> = BTreeMap::new();
1294 for ((family, _), target) in &self.unit_scale_targets {
1295 let rule = match target {
1296 ReferenceTarget::Rule(path) if path.segments.is_empty() => path,
1297 ReferenceTarget::Data(path) if path.segments.is_empty() => {
1298 match self.reference_ends.get(path) {
1299 Some(ReferenceEnd::Rule(rule)) if rule.segments.is_empty() => rule,
1300 Some(ReferenceEnd::Rule(_) | ReferenceEnd::Promptable(_)) | None => {
1301 continue
1302 }
1303 }
1304 }
1305 ReferenceTarget::Rule(_) | ReferenceTarget::Data(_) => continue,
1306 };
1307 scale_rules
1308 .entry(family.clone())
1309 .or_default()
1310 .insert(rule.clone());
1311 }
1312
1313 let mut errors = Vec::new();
1314 for (path, definition) in &self.data {
1315 let DataDefinition::Value {
1316 value,
1317 resolved_type,
1318 source,
1319 ..
1320 } = definition
1321 else {
1322 continue;
1323 };
1324 if !path.segments.is_empty() {
1325 continue;
1326 }
1327 for (family, unit) in unscaled_units(&value.value, resolved_type.as_ref()) {
1328 if let Some(message) = self.foreign_scale_message(&family, &unit) {
1329 errors.push(engine_error_at_graph(self, source, message));
1330 }
1331 }
1332 }
1333
1334 let mut families_by_rule: Vec<(RulePath, BTreeSet<TypeFamily>)> = Vec::new();
1335 for (rule_path, rule_node) in &self.rules {
1336 let expression = rule_node.expression.root();
1337 infer_expression_type(expression, self, inferred_types, resolved_types, cache);
1338 let key = expression.table as *const ExprTable<ExpressionKind> as usize;
1339 let node_types = &cache.tables[&key];
1340 let mut families = BTreeSet::new();
1341 for (index, node_type) in node_types.iter().enumerate() {
1342 if let Some(family) = node_type.held_measure_family() {
1343 families.insert(family.clone());
1344 }
1345 let node = expression.child(ExprId::from_index(index));
1346 let used: Vec<(TypeFamily, String)> = match node.kind() {
1347 ExpressionKind::Literal(literal) => {
1348 unscaled_units(&literal.value, literal.lemma_type.as_ref())
1349 }
1350 ExpressionKind::UnitConversion(
1351 _,
1352 SemanticConversionTarget::Unit {
1353 unit_name,
1354 owning_type,
1355 },
1356 ) if owning_type.unit_is_dynamic(unit_name) => vec![(
1357 owning_type
1358 .measure_family()
1359 .expect("BUG: a dynamic conversion unit belongs to a measure family")
1360 .clone(),
1361 unit_name.clone(),
1362 )],
1363 ExpressionKind::UnitConversion(
1364 inner,
1365 SemanticConversionTarget::Type(ConversionTypeKind::Number),
1366 ) if node_types[inner.index()].is_measure() => {
1367 let source_type = node_types[inner.index()].as_ref();
1368 match source_type.measure_runtime_signature().first() {
1369 Some((unit, _)) if source_type.unit_is_dynamic(unit) => {
1370 vec![(
1371 source_type
1372 .measure_family()
1373 .expect("BUG: a measure with a dynamic unit has a family")
1374 .clone(),
1375 unit.clone(),
1376 )]
1377 }
1378 Some(_) | None => Vec::new(),
1379 }
1380 }
1381 _ => Vec::new(),
1382 };
1383 for (family, unit) in used {
1384 if let Some(message) = self.foreign_scale_message(&family, &unit) {
1385 let source = node.source().unwrap_or(&rule_node.source);
1386 errors.push(engine_error_at_graph(self, source, message));
1387 }
1388 }
1389 }
1390 families_by_rule.push((rule_path.clone(), families));
1391 }
1392
1393 for (rule_path, families) in families_by_rule {
1394 let rule_node = self
1395 .rules
1396 .get_mut(&rule_path)
1397 .expect("BUG: rule from unit-scale planning missing in graph");
1398 for family in &families {
1399 if let Some(rules) = scale_rules.get(family) {
1400 rule_node.depends_on_rules.extend(rules.iter().cloned());
1401 }
1402 }
1403 rule_node.scale_families = families;
1404 }
1405 errors
1406 }
1407
1408 fn foreign_scale_message(&self, family: &TypeFamily, unit: &str) -> Option<String> {
1411 if self
1412 .unit_scale_targets
1413 .contains_key(&(family.clone(), unit.to_string()))
1414 {
1415 return None;
1416 }
1417 Some(format!(
1418 "unit '{unit}' takes its conversion factor from spec '{}' and cannot be used in spec '{}'",
1419 family.spec, self.main_spec.name
1420 ))
1421 }
1422
1423 fn scale_target_lemma_type(
1427 &self,
1428 target: &ReferenceTarget,
1429 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
1430 ) -> Arc<LemmaType> {
1431 let segments = match target {
1432 ReferenceTarget::Data(path) => &path.segments,
1433 ReferenceTarget::Rule(rule) => &rule.segments,
1434 };
1435 if !segments.is_empty() {
1436 let mut identity: Option<&SpecIdentity> = None;
1437 for segment in segments {
1438 let slot = DataPath::local(segment.uses.to_string());
1439 let data = match identity {
1440 None => &self.data,
1441 Some(identity) => &self.instance_lists.templates[identity].data,
1442 };
1443 let Some(DataDefinition::Import { row, .. }) = data.get(&slot) else {
1444 panic!("BUG: unit scale target hop '{slot}' of {target:?} is not a uses slot");
1445 };
1446 identity = Some(row.identity.as_ref());
1447 }
1448 let template = &self.instance_lists.templates
1449 [identity.expect("BUG: a hop scale target has an import identity")];
1450 return match target {
1451 ReferenceTarget::Data(path) => instance_list::row_data_type(
1452 &template.data,
1453 &DataPath::local(path.data.clone()),
1454 ),
1455 ReferenceTarget::Rule(rule) => instance_list::row_end_type(
1456 template,
1457 &RowEnd::Rule(RulePath::new(Vec::new(), rule.rule.clone())),
1458 ),
1459 };
1460 }
1461 match target {
1462 ReferenceTarget::Data(path) => {
1463 let definition = self.data.get(path).unwrap_or_else(|| {
1464 panic!("BUG: resolved unit scale data target '{path}' missing from graph data")
1465 });
1466 Arc::clone(definition.resolved_type_arc().unwrap_or_else(|| {
1467 panic!("BUG: unit scale data target '{path}' has no schema type")
1468 }))
1469 }
1470 ReferenceTarget::Rule(path) => {
1471 if let Some(arc) = inferred_types.get(path) {
1472 return Arc::clone(arc);
1473 }
1474 let rule = self.rules.get(path).unwrap_or_else(|| {
1475 panic!("BUG: resolved unit scale rule target '{path}' missing from graph rules")
1476 });
1477 Arc::clone(&rule.rule_type)
1478 }
1479 }
1480 }
1481
1482 fn check_unit_scale_target_types(
1483 &self,
1484 resolved_types: &ResolvedTypesMap,
1485 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
1486 ) -> Vec<Error> {
1487 let Some(types) = find_types_by_spec(resolved_types, self.main_spec) else {
1488 return Vec::new();
1489 };
1490 let mut errors = Vec::new();
1491 for (type_name, lemma_type) in &types.resolved {
1492 let TypeSpecification::Measure { units, .. } = &lemma_type.specifications else {
1493 continue;
1494 };
1495 let source = self
1496 .main_spec
1497 .data
1498 .iter()
1499 .find(|data| data.reference.is_local() && data.reference.name == *type_name)
1500 .map(|data| &data.source_location)
1501 .or_else(|| {
1502 self.main_spec
1503 .data
1504 .first()
1505 .map(|data| &data.source_location)
1506 })
1507 .or_else(|| {
1508 self.main_spec
1509 .rules
1510 .first()
1511 .map(|rule| &rule.source_location)
1512 })
1513 .expect("BUG: spec with a measure type has a source location");
1514 for unit in units.iter() {
1515 let Some(target) = unit.resolved_scale_target() else {
1516 continue;
1517 };
1518 let target_type = self.scale_target_lemma_type(target, inferred_types);
1519 if !target_type.is_number() {
1520 errors.push(engine_error_at_graph(
1521 self,
1522 source,
1523 format!(
1524 "unit '{}' conversion factor must be a number, got {}",
1525 unit.name,
1526 target_type.name()
1527 ),
1528 ));
1529 }
1530 }
1531 }
1532 errors
1533 }
1534
1535 fn add_stored_row_dependency_edges(&mut self) {
1545 let mut updates: Vec<(RulePath, BTreeSet<RulePath>)> = Vec::new();
1546 for (rule_path, rule_node) in &self.rules {
1547 let edges = self.forced_rules(self.rule_row_reads(rule_node));
1548 if !edges.is_empty() {
1549 updates.push((rule_path.clone(), edges));
1550 }
1551 }
1552 for (rule_path, edges) in updates {
1553 self.rules
1554 .get_mut(&rule_path)
1555 .expect("BUG: rule with stored-row edges missing in graph")
1556 .depends_on_rules
1557 .extend(edges);
1558 }
1559 }
1560
1561 fn rule_row_reads<'s>(&'s self, rule_node: &RuleNode<'_>) -> Vec<ForcedRead<'s>> {
1564 let table = &rule_node.expression.table;
1565 let mut reads: Vec<ForcedRead<'s>> = Vec::new();
1566 let mut visited: HashSet<(ExprId, ScopeKey)> = HashSet::new();
1569 let mut stack: Vec<(ExprId, Vec<Vec<Located<'s>>>)> = Vec::new();
1570 for (condition, result) in &rule_node.branches {
1571 if let Some(condition) = condition {
1572 stack.push((*condition, Vec::new()));
1573 }
1574 stack.push((*result, Vec::new()));
1575 }
1576 while let Some((id, scope)) = stack.pop() {
1577 if !visited.insert((id, scope_key(&scope))) {
1578 continue;
1579 }
1580 match table.kind(id) {
1581 ExpressionKind::RowField { level, end, .. } => {
1582 for row in level_rows(&scope, *level) {
1583 reads.push(ForcedRead {
1584 at: row.clone(),
1585 end: end.clone(),
1586 });
1587 }
1588 }
1589 ExpressionKind::ListCommand {
1590 list,
1591 op,
1592 row_level,
1593 } => {
1594 stack.push((*list, scope.clone()));
1595 let mut inner = scope.clone();
1596 if let Some(level) = row_level {
1597 let depth =
1598 u32::try_from(scope.len()).expect("BUG: row scope depth exceeds u32");
1599 assert!(
1600 *level == depth,
1601 "BUG: list command row level {level} but converted at scope depth {depth}"
1602 );
1603 inner.push(self.expression_rows(table, *list, &scope));
1604 }
1605 match op {
1606 ListOp::Reduce {
1607 argument: Some(argument),
1608 ..
1609 }
1610 | ListOp::Sort {
1611 argument: Some(argument),
1612 ..
1613 } => stack.push((*argument, inner)),
1614 ListOp::Predicate { expr, .. } => stack.push((*expr, inner)),
1615 ListOp::Find { keys } => {
1616 for key in keys {
1617 stack.push((*key, scope.clone()));
1618 }
1619 }
1620 ListOp::Top { n } => stack.push((*n, scope.clone())),
1621 ListOp::With { rows } => {
1622 for row in rows {
1623 stack.push((*row, scope.clone()));
1624 }
1625 }
1626 ListOp::WithRow { fields, .. } => {
1627 for (_, value) in fields {
1628 stack.push((*value, scope.clone()));
1629 }
1630 }
1631 ListOp::First
1632 | ListOp::Last
1633 | ListOp::Count
1634 | ListOp::Key { .. }
1635 | ListOp::Reduce { argument: None, .. }
1636 | ListOp::Sort { argument: None, .. } => {}
1637 }
1638 }
1639 ExpressionKind::SpecMember {
1640 base, path, name, ..
1641 } => {
1642 for row in self.expression_rows(table, *base, &scope) {
1643 if let Some(read) = self.member_read(row, path, name) {
1644 reads.push(read);
1645 }
1646 }
1647 stack.push((*base, scope.clone()));
1648 }
1649 kind => kind.for_each_child(|child| stack.push((child, scope.clone()))),
1650 }
1651 }
1652 reads
1653 }
1654
1655 fn forced_rules<'s>(&'s self, mut reads: Vec<ForcedRead<'s>>) -> BTreeSet<RulePath> {
1657 let mut edges: BTreeSet<RulePath> = BTreeSet::new();
1658 let mut visited: HashSet<(Vec<RowKey>, RowEnd)> = HashSet::new();
1659 while let Some(read) = reads.pop() {
1660 if !visited.insert((read.at.key(), read.end.clone())) {
1661 continue;
1662 }
1663 let row = read.at.row;
1664 let template = self.template_of_row(row);
1665 let slots: Vec<DataPath> = match &read.end {
1666 RowEnd::Data(path) => vec![path.clone()],
1667 RowEnd::Rule(_) => template
1668 .row_end_reads(&read.end)
1669 .into_iter()
1670 .map(|field_read| field_read.slot)
1671 .collect(),
1672 };
1673 for slot in &slots {
1674 if let Some(RowBinding::Deferred { target, hops }) = row.get_binding(template, slot)
1675 {
1676 let owner = read.at.scope(*hops);
1677 self.target_read(owner, target, &mut reads, &mut edges);
1678 }
1679 }
1680 if let RowEnd::Rule(rule) = &read.end {
1681 self.template_rule_reads(template, rule, &read.at, &mut reads, RuleWalk::Follow);
1682 }
1683 }
1684 edges
1685 }
1686
1687 fn reached_imports(&self) -> ShowImports {
1690 let mut out = ShowImports::default();
1691 let mut pending: Vec<(Located<'_>, RulePath)> = Vec::new();
1692 for (rule_path, rule_node) in &self.rules {
1693 let (edges, data) =
1694 self.show_reads(self.rule_row_reads(rule_node), &mut pending, &mut out);
1695 out.edges.insert(rule_path.clone(), edges);
1696 out.data_reads.insert(rule_path.clone(), data);
1697 }
1698 while let Some((at, rule)) = pending.pop() {
1699 let path = RulePath::new(
1700 at.import_segments()
1701 .expect("BUG: a pending Show rule sits on a `uses` import chain"),
1702 rule.rule.clone(),
1703 );
1704 if out.rules.contains_key(&path) {
1705 continue;
1706 }
1707 let template = self.template_of_row(at.row);
1708 out.rules.insert(
1709 path.clone(),
1710 (at.row.identity.as_ref().clone(), rule.clone()),
1711 );
1712 let mut reads = Vec::new();
1713 self.template_rule_reads(template, &rule, &at, &mut reads, RuleWalk::Show);
1714 let (mut edges, data) = self.show_reads(reads, &mut pending, &mut out);
1715 out.need_edges.insert(path.clone(), edges.clone());
1716 let show_rule = template.show_rules.get(&rule).unwrap_or_else(|| {
1717 panic!(
1718 "BUG: rule '{rule}' missing from Show of template '{}'",
1719 template.spec_name
1720 )
1721 });
1722 let mut reads = Vec::new();
1723 for dependency in &show_rule.depends_on_rules {
1724 let at = if dependency.segments.is_empty() {
1725 at.clone()
1726 } else {
1727 let aliases: Vec<String> = dependency
1728 .segments
1729 .iter()
1730 .map(|segment| segment.uses.to_string())
1731 .collect();
1732 self.locate_instance(Some(at.clone()), &aliases)
1733 };
1734 reads.push(ForcedRead {
1735 at,
1736 end: RowEnd::Rule(RulePath::new(Vec::new(), dependency.rule.clone())),
1737 });
1738 }
1739 let (written, written_data) = self.show_reads(reads, &mut pending, &mut out);
1740 assert!(written_data.is_empty(), "BUG: a rule dependency read data");
1741 edges.extend(written);
1742 out.edges.insert(path.clone(), edges);
1743 out.data_reads.insert(path, data);
1744 }
1745 out
1746 }
1747
1748 fn show_reads<'s>(
1753 &'s self,
1754 mut reads: Vec<ForcedRead<'s>>,
1755 pending: &mut Vec<(Located<'s>, RulePath)>,
1756 out: &mut ShowImports,
1757 ) -> (BTreeSet<RulePath>, BTreeSet<DataPath>) {
1758 let mut edges: BTreeSet<RulePath> = BTreeSet::new();
1759 let mut data: BTreeSet<DataPath> = BTreeSet::new();
1760 let mut visited: HashSet<(Vec<RowKey>, RowEnd)> = HashSet::new();
1761 let mut landings: Vec<Landing<'s>> = Vec::new();
1762 loop {
1763 while let Some(landing) = landings.pop() {
1764 match landing {
1765 Landing::Row(read) => reads.push(read),
1766 Landing::Plan(RowEnd::Rule(rule)) => {
1767 edges.insert(rule);
1768 }
1769 Landing::Plan(RowEnd::Data(path)) => match self.data.get(&path) {
1770 Some(DataDefinition::Reference { .. }) => {
1771 match self.reference_ends.get(&path).unwrap_or_else(|| {
1772 panic!("BUG: reference '{path}' missing from reference_ends")
1773 }) {
1774 ReferenceEnd::Rule(rule) => landings.push(
1775 self.target_landing(None, &ReferenceTarget::Rule(rule.clone())),
1776 ),
1777 ReferenceEnd::Promptable(target) => {
1778 landings.push(self.target_landing(
1779 None,
1780 &ReferenceTarget::Data(target.clone()),
1781 ))
1782 }
1783 }
1784 }
1785 Some(DataDefinition::Import { .. }) => {}
1786 Some(definition) => {
1787 if !definition.is_bound() {
1788 data.insert(path);
1789 }
1790 }
1791 None => panic!("BUG: Show read of data '{path}' missing from the plan"),
1792 },
1793 }
1794 }
1795 let Some(read) = reads.pop() else {
1796 break;
1797 };
1798 if !visited.insert((read.at.key(), read.end.clone())) {
1799 continue;
1800 }
1801 let row = read.at.row;
1802 let template = self.template_of_row(row);
1803 let segments = read.at.import_segments();
1804 match (&read.end, segments) {
1805 (RowEnd::Rule(rule), Some(segments)) => {
1806 edges.insert(RulePath::new(segments, rule.rule.clone()));
1807 pending.push((read.at, rule.clone()));
1808 }
1809 (RowEnd::Rule(rule), None) => {
1810 for field_read in template.row_end_reads(&read.end) {
1811 if let Some(RowBinding::Deferred { target, hops }) =
1812 row.get_binding(template, &field_read.slot)
1813 {
1814 landings.push(self.target_landing(read.at.scope(*hops), target));
1815 }
1816 }
1817 self.template_rule_reads(
1818 template,
1819 rule,
1820 &read.at,
1821 &mut reads,
1822 RuleWalk::Follow,
1823 );
1824 }
1825 (RowEnd::Data(slot), segments) => {
1826 match row.get_binding(template, slot) {
1827 Some(RowBinding::Deferred { target, hops }) => {
1828 landings.push(self.target_landing(read.at.scope(*hops), target));
1829 }
1830 Some(_) => {}
1831 None => {
1832 let Some(segments) = segments else {
1833 continue;
1834 };
1835 match template.data.get(slot) {
1836 Some(DataDefinition::Reference { .. }) => {
1837 let target = match template.reference_ends.get(slot).unwrap_or_else(
1838 || panic!("BUG: reference '{slot}' missing from reference_ends"),
1839 ) {
1840 ReferenceEnd::Rule(rule) => ReferenceTarget::Rule(rule.clone()),
1841 ReferenceEnd::Promptable(target) => {
1842 ReferenceTarget::Data(target.clone())
1843 }
1844 };
1845 landings
1846 .push(self.target_landing(Some(read.at.clone()), &target));
1847 }
1848 Some(DataDefinition::Import { .. }) => {}
1849 Some(definition) => {
1850 if !definition.is_bound() {
1851 let path = DataPath {
1852 segments,
1853 data: slot.data.clone(),
1854 };
1855 out.data
1856 .insert(path.clone(), row.identity.as_ref().clone());
1857 data.insert(path);
1858 }
1859 }
1860 None => panic!(
1861 "BUG: Show read of '{slot}' missing from template '{}'",
1862 template.spec_name
1863 ),
1864 }
1865 }
1866 }
1867 }
1868 }
1869 }
1870 (edges, data)
1871 }
1872
1873 fn target_read<'s>(
1876 &'s self,
1877 owner: Option<Located<'s>>,
1878 target: &ReferenceTarget,
1879 reads: &mut Vec<ForcedRead<'s>>,
1880 edges: &mut BTreeSet<RulePath>,
1881 ) {
1882 match self.target_landing(owner, target) {
1883 Landing::Row(read) => reads.push(read),
1884 Landing::Plan(RowEnd::Rule(rule)) => {
1885 edges.insert(rule);
1886 }
1887 Landing::Plan(RowEnd::Data(_)) => {}
1888 }
1889 }
1890
1891 fn target_landing<'s>(
1893 &'s self,
1894 owner: Option<Located<'s>>,
1895 target: &ReferenceTarget,
1896 ) -> Landing<'s> {
1897 let (segments, end) = match target {
1898 ReferenceTarget::Rule(rule) => (
1899 &rule.segments,
1900 RowEnd::Rule(RulePath::new(Vec::new(), rule.rule.clone())),
1901 ),
1902 ReferenceTarget::Data(path) => (
1903 &path.segments,
1904 RowEnd::Data(DataPath::local(path.data.clone())),
1905 ),
1906 };
1907 let aliases: Vec<String> = segments
1908 .iter()
1909 .map(|segment| {
1910 assert!(
1911 segment.row.is_none(),
1912 "BUG: reference target hops are `uses` slots, got row hop `{}`",
1913 segment.input_key()
1914 );
1915 segment.uses.to_string()
1916 })
1917 .collect();
1918 let at = match (owner, aliases.is_empty()) {
1919 (None, true) => return Landing::Plan(end),
1920 (owner, _) if aliases.is_empty() => owner.expect("BUG: owner checked above"),
1921 (owner, _) => self.locate_instance(owner, &aliases),
1922 };
1923 Landing::Row(ForcedRead { at, end })
1924 }
1925
1926 fn template_rule_reads<'s>(
1935 &'s self,
1936 template: &'s ExecutionPlan,
1937 rule: &RulePath,
1938 at: &Located<'s>,
1939 reads: &mut Vec<ForcedRead<'s>>,
1940 walk: RuleWalk,
1941 ) {
1942 let mut rules: Vec<RulePath> = vec![rule.clone()];
1943 let mut walked: BTreeSet<RulePath> = BTreeSet::new();
1944 let mut visited: HashSet<(NormalFormId, ScopeKey)> = HashSet::new();
1945 while let Some(rule) = rules.pop() {
1946 if !walked.insert(rule.clone()) {
1947 continue;
1948 }
1949 let root = template
1950 .rules
1951 .get(&rule)
1952 .unwrap_or_else(|| {
1953 panic!(
1954 "BUG: row rule '{rule}' missing from template plan '{}'",
1955 template.spec_name
1956 )
1957 })
1958 .normal_form;
1959 let mut stack: Vec<(NormalFormId, Vec<Vec<Located<'s>>>)> = vec![(root, Vec::new())];
1960 while let Some((id, scope)) = stack.pop() {
1961 if !visited.insert((id, scope_key(&scope))) {
1962 continue;
1963 }
1964 let nf = template.normal_form(id);
1965 if let Some(target) = &nf.rule_ref {
1966 match walk {
1967 RuleWalk::Follow => rules.push(target.clone()),
1968 RuleWalk::Show => reads.push(ForcedRead {
1969 at: at.clone(),
1970 end: RowEnd::Rule(target.clone()),
1971 }),
1972 }
1973 continue;
1974 }
1975 match &nf.kind {
1976 NormalFormKind::Leaf(LeafKind::DataPath(path)) if walk == RuleWalk::Show => {
1977 if !matches!(template.data.get(path), Some(DataDefinition::Import { .. })) {
1978 reads.push(ForcedRead {
1979 at: at.clone(),
1980 end: RowEnd::Data(path.clone()),
1981 });
1982 }
1983 if let Some(options) = template.options_rule_of(path) {
1984 reads.push(ForcedRead {
1985 at: at.clone(),
1986 end: RowEnd::Rule(options),
1987 });
1988 }
1989 }
1990 NormalFormKind::RowField { level, end, .. } => {
1991 for row in level_rows(&scope, *level) {
1992 reads.push(ForcedRead {
1993 at: row.clone(),
1994 end: end.clone(),
1995 });
1996 }
1997 }
1998 NormalFormKind::SpecMember { base, row, .. } => {
1999 let (last, hops) = row
2000 .ends
2001 .split_last()
2002 .expect("BUG: a row read has a last end");
2003 for base_row in self.normal_cell_rows(template, *base, at, &scope) {
2004 let mut held = base_row;
2005 for hop in hops {
2006 let RowEnd::Data(slot) = hop else {
2007 panic!("BUG: a row read hop is a `uses` slot, got {hop:?}");
2008 };
2009 held = self.import_hop(held, slot);
2010 }
2011 reads.push(ForcedRead {
2012 at: held,
2013 end: last.clone(),
2014 });
2015 }
2016 stack.push((*base, scope.clone()));
2017 }
2018 NormalFormKind::ListCommand {
2019 list,
2020 args,
2021 row_level,
2022 ..
2023 } => {
2024 stack.push((*list, scope.clone()));
2025 let mut inner = scope.clone();
2026 if let Some(level) = row_level {
2027 let depth = u32::try_from(scope.len())
2028 .expect("BUG: row scope depth exceeds u32");
2029 assert!(
2030 *level == depth,
2031 "BUG: list command row level {level} but walked at scope depth {depth}"
2032 );
2033 inner.push(self.normal_cell_rows(template, *list, at, &scope));
2034 }
2035 for arg in args {
2036 stack.push((*arg, inner.clone()));
2037 }
2038 }
2039 kind => kind.for_each_child(|child| stack.push((child, scope.clone()))),
2040 }
2041 }
2042 }
2043 }
2044
2045 fn normal_cell_rows<'s>(
2049 &'s self,
2050 template: &'s ExecutionPlan,
2051 id: NormalFormId,
2052 at: &Located<'s>,
2053 scope: &[Vec<Located<'s>>],
2054 ) -> Vec<Located<'s>> {
2055 let mut memo: HashMap<NormalFormId, Vec<Located<'s>>> = HashMap::new();
2056 let mut stack: Vec<(NormalFormId, bool)> = vec![(id, false)];
2057 while let Some((cell, ready)) = stack.pop() {
2058 if memo.contains_key(&cell) {
2059 continue;
2060 }
2061 let nf = template.normal_form(cell);
2062 let children: Vec<NormalFormId> = if nf.rule_ref.is_some() {
2063 Vec::new()
2064 } else {
2065 match &nf.kind {
2066 NormalFormKind::SpecMember { base, .. } => vec![*base],
2067 NormalFormKind::ListCommand {
2068 list,
2069 op: NormalFormListOp::With,
2070 args,
2071 ..
2072 } => std::iter::once(*list).chain(args.iter().copied()).collect(),
2073 NormalFormKind::ListCommand { list, .. } => vec![*list],
2074 NormalFormKind::Piecewise(arms) => arms.iter().map(|(_, body)| *body).collect(),
2075 _ => Vec::new(),
2076 }
2077 };
2078 if !ready {
2079 stack.push((cell, true));
2080 stack.extend(children.into_iter().map(|child| (child, false)));
2081 continue;
2082 }
2083 let child_rows = |memo: &HashMap<NormalFormId, Vec<Located<'s>>>| -> Vec<Located<'s>> {
2084 children
2085 .iter()
2086 .flat_map(|child| memo[child].iter().cloned())
2087 .collect()
2088 };
2089 let rows = match (&nf.rule_ref, &nf.kind) {
2090 (Some(rule), _) => self.end_rows(at.clone(), &RowEnd::Rule(rule.clone())),
2091 (None, NormalFormKind::Leaf(LeafKind::DataPath(path))) => {
2092 self.end_rows(at.clone(), &RowEnd::Data(path.clone()))
2093 }
2094 (None, NormalFormKind::Leaf(LeafKind::RowValue { level })) => {
2095 level_rows(scope, *level).to_vec()
2096 }
2097 (None, NormalFormKind::RowField { level, end, .. }) => level_rows(scope, *level)
2098 .iter()
2099 .flat_map(|row| self.end_rows(row.clone(), end))
2100 .collect(),
2101 (None, NormalFormKind::SpecMember { row, .. }) => child_rows(&memo)
2102 .into_iter()
2103 .flat_map(|base| self.ends_rows(base, &row.ends))
2104 .collect(),
2105 (None, NormalFormKind::ListCommand { .. } | NormalFormKind::Piecewise(_)) => {
2106 child_rows(&memo)
2107 }
2108 (None, _) => Vec::new(),
2109 };
2110 memo.insert(cell, rows);
2111 }
2112 memo.remove(&id)
2113 .expect("BUG: the requested cell is resolved last")
2114 }
2115
2116 fn expression_rows<'s>(
2120 &'s self,
2121 table: &ExprTable<ExpressionKind>,
2122 id: ExprId,
2123 scope: &[Vec<Located<'s>>],
2124 ) -> Vec<Located<'s>> {
2125 let mut memo: HashMap<ExprId, Vec<Located<'s>>> = HashMap::new();
2126 let mut stack: Vec<(ExprId, bool)> = vec![(id, false)];
2127 while let Some((at, ready)) = stack.pop() {
2128 if memo.contains_key(&at) {
2129 continue;
2130 }
2131 let children: Vec<ExprId> = match table.kind(at) {
2132 ExpressionKind::SpecMember { base, .. } => vec![*base],
2133 ExpressionKind::ListCommand {
2134 list,
2135 op: ListOp::With { rows },
2136 ..
2137 } => std::iter::once(*list).chain(rows.iter().copied()).collect(),
2138 ExpressionKind::ListCommand { list, .. } => vec![*list],
2139 ExpressionKind::Piecewise(arms) => arms.iter().map(|(_, body)| *body).collect(),
2140 _ => Vec::new(),
2141 };
2142 if !ready {
2143 stack.push((at, true));
2144 stack.extend(children.into_iter().map(|child| (child, false)));
2145 continue;
2146 }
2147 let child_rows: Vec<Located<'s>> = children
2148 .iter()
2149 .flat_map(|child| memo[child].iter().cloned())
2150 .collect();
2151 let rows = match table.kind(at) {
2152 ExpressionKind::DataPath(path) => self.plan_slot_rows(path),
2153 ExpressionKind::RulePath(rule) => match self.rule_candidates.get(rule) {
2154 Some(candidates) => candidates
2155 .instances
2156 .iter()
2157 .map(|instance| self.locate_instance(None, instance))
2158 .collect(),
2159 None => {
2160 assert!(
2161 self.dropped_rules.contains(rule),
2162 "BUG: rule '{rule}' is linked but has no candidates"
2163 );
2164 Vec::new()
2165 }
2166 },
2167 ExpressionKind::RowValue { level, .. } => level_rows(scope, *level).to_vec(),
2168 ExpressionKind::RowField { level, end, .. } => level_rows(scope, *level)
2169 .iter()
2170 .flat_map(|row| self.end_rows(row.clone(), end))
2171 .collect(),
2172 ExpressionKind::SpecMember { path, name, .. } => child_rows
2173 .into_iter()
2174 .flat_map(|base| {
2175 let ends = self.member_ends(base.row, path, name);
2176 match ends {
2177 Some(ends) => self.ends_rows(base, &ends),
2178 None => Vec::new(),
2179 }
2180 })
2181 .collect(),
2182 ExpressionKind::ListCommand { .. } | ExpressionKind::Piecewise(_) => child_rows,
2183 _ => Vec::new(),
2184 };
2185 memo.insert(at, rows);
2186 }
2187 memo.remove(&id)
2188 .expect("BUG: the requested expression is resolved last")
2189 }
2190
2191 fn plan_slot_rows(&self, path: &DataPath) -> Vec<Located<'_>> {
2194 match self.data.get(path) {
2195 Some(DataDefinition::Import { row, .. }) => vec![Located::root(row)],
2196 Some(DataDefinition::InstanceList { .. }) => self
2197 .instance_lists
2198 .sources
2199 .get(path)
2200 .expect("BUG: instance list data has a source")
2201 .store
2202 .shape_rows()
2203 .map(|row| Located::root(row))
2204 .collect(),
2205 _ => Vec::new(),
2206 }
2207 }
2208
2209 fn member_ends(&self, row: &RowInstance, path: &[String], name: &str) -> Option<Vec<RowEnd>> {
2212 let parts: Vec<&str> = path
2213 .iter()
2214 .map(String::as_str)
2215 .chain(std::iter::once(name))
2216 .collect();
2217 let template = self.template_of_row(row);
2218 instance_list::row_end_chain(
2219 &row.identity,
2220 instance_list::RowEndView::Plan(template),
2221 &self.instance_lists.templates,
2222 &parts,
2223 )
2224 .map(|chain| chain.into_iter().map(|hop| hop.end).collect())
2225 }
2226
2227 fn member_read<'s>(
2229 &'s self,
2230 at: Located<'s>,
2231 path: &[String],
2232 name: &str,
2233 ) -> Option<ForcedRead<'s>> {
2234 let ends = self.member_ends(at.row, path, name)?;
2235 let (last, hops) = ends
2236 .split_last()
2237 .expect("BUG: a member chain has a last end");
2238 let mut at = at;
2239 for hop in hops {
2240 let RowEnd::Data(slot) = hop else {
2241 panic!("BUG: a member chain hop is a `uses` slot, got {hop:?}");
2242 };
2243 at = self.import_hop(at, slot);
2244 }
2245 Some(ForcedRead {
2246 at,
2247 end: last.clone(),
2248 })
2249 }
2250
2251 fn ends_rows<'s>(&'s self, at: Located<'s>, ends: &[RowEnd]) -> Vec<Located<'s>> {
2253 let (last, hops) = ends.split_last().expect("BUG: a row read has a last end");
2254 let mut at = at;
2255 for hop in hops {
2256 let RowEnd::Data(slot) = hop else {
2257 panic!("BUG: a row read hop is a `uses` slot, got {hop:?}");
2258 };
2259 at = self.import_hop(at, slot);
2260 }
2261 self.end_rows(at, last)
2262 }
2263
2264 fn end_rows<'s>(&'s self, at: Located<'s>, end: &RowEnd) -> Vec<Located<'s>> {
2268 let template = self.template_of_row(at.row);
2269 match end {
2270 RowEnd::Rule(rule) => template
2271 .rule_candidates
2272 .get(rule)
2273 .unwrap_or_else(|| {
2274 panic!(
2275 "BUG: rule '{rule}' of '{}' has no candidate list",
2276 template.spec_name
2277 )
2278 })
2279 .instances
2280 .iter()
2281 .map(|instance| self.locate_instance(Some(at.clone()), instance))
2282 .collect(),
2283 RowEnd::Data(path) => match template.data.get(path) {
2284 Some(DataDefinition::Import { .. }) => vec![self.import_hop(at, path)],
2285 Some(DataDefinition::InstanceList { .. }) => {
2286 match at.row.get_binding(template, path) {
2287 Some(RowBinding::Rows { rows, hops, .. }) => {
2288 let owner = at.context_at(*hops);
2289 rows.iter()
2290 .map(|row| Located {
2291 row,
2292 context: owner.clone(),
2293 })
2294 .collect()
2295 }
2296 Some(RowBinding::Bound(_)) | None => {
2297 let context = at.inside();
2298 template
2299 .instance_lists
2300 .get(path)
2301 .expect("BUG: template instance list has a source")
2302 .store
2303 .shape_rows()
2304 .map(|row| Located {
2305 row,
2306 context: context.clone(),
2307 })
2308 .collect()
2309 }
2310 Some(binding) => {
2311 panic!("BUG: list field '{path}' holds {binding:?}")
2312 }
2313 }
2314 }
2315 _ => Vec::new(),
2316 },
2317 }
2318 }
2319
2320 fn import_hop<'s>(&'s self, at: Located<'s>, slot: &DataPath) -> Located<'s> {
2323 let template = self.template_of_row(at.row);
2324 let row = match at.row.get_binding(template, slot) {
2325 Some(RowBinding::Instance(row)) => row.as_ref(),
2326 None => match template.data.get(slot) {
2327 Some(DataDefinition::Import { row, .. }) => row.as_ref(),
2328 other => panic!("BUG: `uses` hop '{slot}' is not a `uses` slot: {other:?}"),
2329 },
2330 Some(binding) => panic!("BUG: `uses` slot '{slot}' holds {binding:?}"),
2331 };
2332 Located {
2333 row,
2334 context: at.inside(),
2335 }
2336 }
2337
2338 fn locate_instance<'s>(&'s self, from: Option<Located<'s>>, aliases: &[String]) -> Located<'s> {
2340 let (first, rest) = aliases
2341 .split_first()
2342 .expect("BUG: an instance has an alias");
2343 let first = DataPath::local(first.clone());
2344 let mut at = match from {
2345 Some(from) => self.import_hop(from, &first),
2346 None => match self.data.get(&first) {
2347 Some(DataDefinition::Import { row, .. }) => Located::root(row),
2348 other => panic!("BUG: instance `{first}` is not a `uses` of the plan: {other:?}"),
2349 },
2350 };
2351 for alias in rest {
2352 at = self.import_hop(at, &DataPath::local(alias.clone()));
2353 }
2354 at
2355 }
2356
2357 fn template_of_row(&self, row: &RowInstance) -> &ExecutionPlan {
2359 self.instance_lists
2360 .templates
2361 .get(&row.identity)
2362 .expect("BUG: rows are rows of a planned row spec")
2363 }
2364
2365 fn compute_reference_ends(&self) -> IndexMap<DataPath, ReferenceEnd> {
2369 let mut out: IndexMap<DataPath, ReferenceEnd> = IndexMap::new();
2370 for (path, def) in &self.data {
2371 let DataDefinition::Reference { target, .. } = def else {
2372 continue;
2373 };
2374 match target {
2375 ReferenceTarget::Rule(rule_path) => {
2376 out.insert(path.clone(), ReferenceEnd::Rule(rule_path.clone()));
2377 }
2378 ReferenceTarget::Data(start) => {
2379 out.insert(path.clone(), self.expect_reference_end(start));
2380 }
2381 }
2382 }
2383 out
2384 }
2385
2386 fn expect_reference_end(&self, start: &DataPath) -> ReferenceEnd {
2389 let mut path = start;
2390 for _ in 0..=self.data.len() {
2391 match self.data.get(path) {
2392 Some(
2393 DataDefinition::Value { .. }
2394 | DataDefinition::TypeDeclaration { .. }
2395 | DataDefinition::InstanceList { .. },
2396 ) => return ReferenceEnd::Promptable(path.clone()),
2397 Some(DataDefinition::Reference {
2398 target: ReferenceTarget::Rule(rule),
2399 ..
2400 }) => return ReferenceEnd::Rule(rule.clone()),
2401 Some(DataDefinition::Reference {
2402 target: ReferenceTarget::Data(next),
2403 ..
2404 }) => path = next,
2405 Some(DataDefinition::Import { .. }) => {
2406 panic!("BUG: data reference chain from '{start}' ends at import '{path}'")
2407 }
2408 None => {
2409 panic!("BUG: data-target reference chain from '{start}' ends at missing data '{path}'")
2410 }
2411 }
2412 }
2413 panic!("BUG: data reference chain from '{start}' outlasts plan data (cycles are rejected)")
2414 }
2415
2416 fn compute_data_reference_order(&self) -> Result<Vec<DataPath>, Vec<Error>> {
2423 let reference_paths: Vec<DataPath> = self
2424 .data
2425 .iter()
2426 .filter_map(|(p, d)| match d {
2427 DataDefinition::Reference {
2428 target: ReferenceTarget::Data(_),
2429 ..
2430 } => Some(p.clone()),
2431 _ => None,
2432 })
2433 .collect();
2434
2435 if reference_paths.is_empty() {
2436 return Ok(Vec::new());
2437 }
2438
2439 let reference_set: BTreeSet<DataPath> = reference_paths.iter().cloned().collect();
2440 let mut in_degree: BTreeMap<DataPath, usize> = BTreeMap::new();
2441 let mut dependents: BTreeMap<DataPath, Vec<DataPath>> = BTreeMap::new();
2442 for p in &reference_paths {
2443 in_degree.insert(p.clone(), 0);
2444 dependents.insert(p.clone(), Vec::new());
2445 }
2446
2447 for p in &reference_paths {
2448 let Some(DataDefinition::Reference {
2449 target: ReferenceTarget::Data(target_path),
2450 ..
2451 }) = self.data.get(p)
2452 else {
2453 unreachable!("BUG: reference path collected above is not a data-target reference");
2454 };
2455 if reference_set.contains(target_path) {
2456 *in_degree
2457 .get_mut(p)
2458 .expect("BUG: reference missing in_degree") += 1;
2459 dependents
2460 .get_mut(target_path)
2461 .expect("BUG: reference missing dependents list")
2462 .push(p.clone());
2463 }
2464 }
2465
2466 let mut queue: VecDeque<DataPath> = in_degree
2467 .iter()
2468 .filter(|(_, d)| **d == 0)
2469 .map(|(p, _)| p.clone())
2470 .collect();
2471
2472 let mut result: Vec<DataPath> = Vec::new();
2473 while let Some(path) = queue.pop_front() {
2474 result.push(path.clone());
2475 if let Some(deps) = dependents.get(&path) {
2476 for dependent in deps.clone() {
2477 let degree = in_degree
2478 .get_mut(&dependent)
2479 .expect("BUG: reference dependent missing in_degree");
2480 *degree -= 1;
2481 if *degree == 0 {
2482 queue.push_back(dependent);
2483 }
2484 }
2485 }
2486 }
2487
2488 if result.len() != reference_paths.len() {
2489 let cycle_members: Vec<DataPath> = reference_paths
2490 .iter()
2491 .filter(|p| !result.contains(p))
2492 .cloned()
2493 .collect();
2494 let cycle_display: String = cycle_members
2495 .iter()
2496 .map(|p| p.to_string())
2497 .collect::<Vec<_>>()
2498 .join(", ");
2499 let errors: Vec<Error> = cycle_members
2500 .iter()
2501 .map(|p| {
2502 let source = self
2503 .data
2504 .get(p)
2505 .expect("BUG: cycle member collected from self.data")
2506 .source();
2507 reference_error(
2508 self.main_spec,
2509 source,
2510 format!("Circular data reference ({})", cycle_display),
2511 )
2512 })
2513 .collect();
2514 return Err(errors);
2515 }
2516
2517 Ok(result)
2518 }
2519
2520 fn check_binding_reference_cycles(&self) -> Result<(), Vec<Error>> {
2525 enum Step<'s> {
2526 Row(Located<'s>, DataPath),
2527 Plan(DataPath),
2528 }
2529 let mut errors = Vec::new();
2530 let mut reported: BTreeSet<Vec<String>> = BTreeSet::new();
2531 let mut rows: Vec<(Located<'_>, &Source)> = Vec::new();
2532 for definition in self.data.values() {
2533 if let DataDefinition::Import { row, source, .. } = definition {
2534 rows.push((Located::root(row), source));
2535 }
2536 }
2537 while let Some((at, source)) = rows.pop() {
2538 let template = self.template_of_row(at.row);
2539 for (slot, binding) in at.row.bindings.iter().enumerate() {
2540 match binding {
2541 Some(RowBinding::Instance(nested)) => rows.push((
2542 Located {
2543 row: nested,
2544 context: at.inside(),
2545 },
2546 source,
2547 )),
2548 Some(RowBinding::Deferred {
2549 target: ReferenceTarget::Data(_),
2550 ..
2551 }) => {
2552 let (path, _) = template
2553 .data
2554 .get_index(slot)
2555 .expect("BUG: row binding slot is data of its template");
2556 let mut chain: Vec<(Vec<RowKey>, DataPath)> = Vec::new();
2557 let mut names: Vec<String> = Vec::new();
2558 let mut step = Step::Row(at.clone(), path.clone());
2559 loop {
2560 let landing = match step {
2561 Step::Plan(path) => match self.data.get(&path) {
2562 Some(DataDefinition::Reference {
2563 target: target @ ReferenceTarget::Data(_),
2564 ..
2565 }) => self.target_landing(None, target),
2566 _ => break,
2567 },
2568 Step::Row(at, slot) => {
2569 let read = (at.key(), slot.clone());
2570 if let Some(start) = chain.iter().position(|seen| *seen == read)
2571 {
2572 let mut cycle = names[start..].to_vec();
2573 cycle.sort();
2574 if reported.insert(cycle.clone()) {
2575 errors.push(reference_error(
2576 self.main_spec,
2577 source,
2578 format!(
2579 "Circular data reference ({})",
2580 cycle.join(", ")
2581 ),
2582 ));
2583 }
2584 break;
2585 }
2586 let segments = at.import_segments().expect(
2587 "BUG: binding cycles walk `uses` rows only: imports, their nested instances, and `uses` hops",
2588 );
2589 names.push(
2590 DataPath::new(segments, slot.data.clone()).input_key(),
2591 );
2592 chain.push(read);
2593 let template = self.template_of_row(at.row);
2594 match at.row.get_binding(template, &slot) {
2595 Some(RowBinding::Deferred {
2596 target: target @ ReferenceTarget::Data(_),
2597 hops,
2598 }) => self.target_landing(at.scope(*hops), target),
2599 Some(_) => break,
2600 None => match template.reference_ends.get(&slot) {
2601 Some(ReferenceEnd::Promptable(end)) => self
2602 .target_landing(
2603 Some(at.clone()),
2604 &ReferenceTarget::Data(end.clone()),
2605 ),
2606 Some(ReferenceEnd::Rule(_)) | None => break,
2607 },
2608 }
2609 }
2610 };
2611 step = match landing {
2612 Landing::Row(ForcedRead {
2613 at,
2614 end: RowEnd::Data(slot),
2615 }) => Step::Row(at, slot),
2616 Landing::Plan(RowEnd::Data(path)) => Step::Plan(path),
2617 Landing::Row(ForcedRead {
2618 end: RowEnd::Rule(_),
2619 ..
2620 })
2621 | Landing::Plan(RowEnd::Rule(_)) => break,
2622 };
2623 }
2624 }
2625 Some(_) | None => {}
2626 }
2627 }
2628 }
2629 if errors.is_empty() {
2630 Ok(())
2631 } else {
2632 Err(errors)
2633 }
2634 }
2635
2636 fn link_spec_values(&mut self) -> Result<Vec<Error>, Vec<Error>> {
2641 use crate::planning::spec_value::{
2642 assert_expression_candidates_linked, spec_member_errors, CandidateScope, CandidateTable,
2643 };
2644
2645 let rule_paths: HashSet<RulePath> = self.rules.keys().cloned().collect();
2646 let dropped_rules = self.dropped_rules.clone();
2647 let mut all_rules: BTreeSet<RulePath> = rule_paths.iter().cloned().collect();
2648 all_rules.extend(dropped_rules.iter().cloned());
2649 let presence = move |rule: &RulePath| {
2650 if rule_paths.contains(rule) {
2651 RulePresence::Present
2652 } else if dropped_rules.contains(rule) {
2653 RulePresence::Dropped
2654 } else {
2655 RulePresence::Absent
2656 }
2657 };
2658 let order = self.topological_sort()?;
2659 let row_end_reads =
2660 |identity: &SpecIdentity, end: &RowEnd| self.row_end_reads(identity, end);
2661 let mut previous: HashMap<RulePath, SpecCandidates> = HashMap::new();
2662 loop {
2663 let mut rule_candidates = previous.clone();
2664 let mut member_errors = Vec::new();
2665 for path in &order {
2666 let node = self
2667 .rules
2668 .get(path)
2669 .expect("BUG: rule from topological sort not in graph");
2670 let scope = CandidateScope {
2671 rule_candidates: &rule_candidates,
2672 data: &self.data,
2673 rules: &all_rules,
2674 sources: &self.instance_lists.sources,
2675 rule_presence: &presence,
2676 row_end_reads: &row_end_reads,
2677 self_identity: &self.instance_lists.self_identity,
2678 templates: &self.instance_lists.templates,
2679 };
2680 let table = CandidateTable::build(&node.expression, &scope);
2681 let mut candidates = SpecCandidates::default();
2682 for (_, result) in &node.branches {
2683 candidates.extend(table.candidates(*result));
2684 }
2685 for (source, message) in spec_member_errors(&node.expression, &table, &scope) {
2686 let source = source.expect("BUG: spec member expression carries its source");
2687 member_errors.push(Error::validation_with_context(
2688 message,
2689 Some(source),
2690 None::<String>,
2691 Some(self.main_spec),
2692 None,
2693 ));
2694 }
2695 rule_candidates.insert(path.clone(), candidates);
2696 }
2697 if rule_candidates == previous {
2698 let scope = CandidateScope {
2699 rule_candidates: &rule_candidates,
2700 data: &self.data,
2701 rules: &all_rules,
2702 sources: &self.instance_lists.sources,
2703 rule_presence: &presence,
2704 row_end_reads: &row_end_reads,
2705 self_identity: &self.instance_lists.self_identity,
2706 templates: &self.instance_lists.templates,
2707 };
2708 for node in self.rules.values() {
2709 assert_expression_candidates_linked(&node.expression, &scope);
2710 }
2711 self.rule_candidates = rule_candidates;
2712 return Ok(member_errors);
2713 }
2714 previous = rule_candidates;
2715 }
2716 }
2717
2718 fn row_end_reads(&self, identity: &SpecIdentity, end: &RowEnd) -> Vec<RowFieldRead> {
2722 match self.instance_lists.row_template_of(identity) {
2723 Some(RowTemplate::Plan(template)) => template.row_end_reads(end),
2724 Some(RowTemplate::Root) => self.root_row_end_reads(end),
2725 None => Vec::new(),
2726 }
2727 }
2728
2729 fn root_row_end_reads(&self, end: &RowEnd) -> Vec<RowFieldRead> {
2730 let mut reads: Vec<RowFieldRead> = Vec::new();
2731 let push = |path: &DataPath, reads: &mut Vec<RowFieldRead>| {
2732 if let Some(read) = self.root_row_slot(path) {
2733 if !reads.contains(&read) {
2734 reads.push(read);
2735 }
2736 }
2737 };
2738 let rule_of_data = |path: &DataPath| -> Option<RulePath> {
2739 let mut current = path;
2740 for _ in 0..=self.data.len() {
2741 match self.data.get(current)? {
2742 DataDefinition::Reference {
2743 target: ReferenceTarget::Data(next),
2744 ..
2745 } => current = next,
2746 DataDefinition::Reference {
2747 target: ReferenceTarget::Rule(rule),
2748 ..
2749 } => return Some(rule.clone()),
2750 _ => return None,
2751 }
2752 }
2753 panic!(
2754 "BUG: data reference chain from '{path}' outlasts graph data (cycles are rejected)"
2755 )
2756 };
2757 let start_rule = match end {
2758 RowEnd::Data(path) => match rule_of_data(path) {
2759 Some(rule) => Some(rule),
2760 None => {
2761 push(path, &mut reads);
2762 None
2763 }
2764 },
2765 RowEnd::Rule(rule) => Some(rule.clone()),
2766 };
2767 match start_rule {
2768 None => {}
2769 Some(rule) => {
2770 let mut pending = vec![rule];
2771 let mut seen: BTreeSet<RulePath> = BTreeSet::new();
2772 while let Some(rule) = pending.pop() {
2773 if !seen.insert(rule.clone()) {
2774 continue;
2775 }
2776 let Some(node) = self.rules.get(&rule) else {
2777 continue;
2778 };
2779 for id in node.expression.table.ids() {
2780 if let ExpressionKind::DataPath(path) = node.expression.table.kind(id) {
2781 push(path, &mut reads);
2782 }
2783 }
2784 pending.extend(node.depends_on_rules.iter().cloned());
2785 }
2786 }
2787 }
2788 reads
2789 }
2790
2791 fn root_row_slot(&self, path: &DataPath) -> Option<RowFieldRead> {
2795 let mut current = path;
2796 for _ in 0..=self.data.len() {
2797 match self.data.get(current)? {
2798 DataDefinition::Reference {
2799 target: ReferenceTarget::Data(next),
2800 local_value: None,
2801 ..
2802 } => current = next,
2803 DataDefinition::Reference { .. } | DataDefinition::Import { .. } => return None,
2804 definition @ (DataDefinition::TypeDeclaration { .. }
2805 | DataDefinition::Value { .. }
2806 | DataDefinition::InstanceList { .. }) => {
2807 return (!definition.is_bound()).then(|| RowFieldRead {
2808 slot: current.clone(),
2809 unbound: definition.value().is_none(),
2810 });
2811 }
2812 }
2813 }
2814 panic!("BUG: data reference chain from '{path}' outlasts graph data (cycles are rejected)")
2815 }
2816
2817 fn present_rule_dependencies(&self, dependencies: Vec<RulePath>) -> BTreeSet<RulePath> {
2819 let mut present = BTreeSet::new();
2820 for dependency in dependencies {
2821 match self.rule_presence(&dependency) {
2822 RulePresence::Present => {
2823 present.insert(dependency);
2824 }
2825 RulePresence::Dropped => {}
2826 RulePresence::Absent => {
2827 panic!("BUG: rule '{dependency}' neither in graph nor dropped")
2828 }
2829 }
2830 }
2831 present
2832 }
2833
2834 fn sort_show_rules(&mut self) -> Result<(), Vec<Error>> {
2838 let show_imports = self.reached_imports();
2839 let mut edges: BTreeMap<RulePath, BTreeSet<RulePath>> = BTreeMap::new();
2840 for (path, node) in &self.rules {
2841 let mut rule_edges: BTreeSet<RulePath> = node
2842 .depends_on_rules
2843 .iter()
2844 .filter(|dependency| self.rules.contains_key(*dependency))
2845 .cloned()
2846 .collect();
2847 rule_edges.extend(show_imports.edges.get(path).into_iter().flatten().cloned());
2848 edges.insert(path.clone(), rule_edges);
2849 }
2850 for path in show_imports.rules.keys() {
2851 edges.insert(
2852 path.clone(),
2853 show_imports
2854 .edges
2855 .get(path)
2856 .expect("BUG: reached import rule has Show edges")
2857 .clone(),
2858 );
2859 }
2860 let mut in_degree: BTreeMap<&RulePath, usize> = BTreeMap::new();
2861 let mut dependents: BTreeMap<&RulePath, Vec<&RulePath>> = BTreeMap::new();
2862 for (path, dependencies) in &edges {
2863 in_degree.insert(path, dependencies.len());
2864 for dependency in dependencies {
2865 assert!(
2866 edges.contains_key(dependency),
2867 "BUG: Show edge '{path}' -> '{dependency}' leaves the Show rules"
2868 );
2869 dependents.entry(dependency).or_default().push(path);
2870 }
2871 }
2872 let mut queue: VecDeque<&RulePath> = in_degree
2873 .iter()
2874 .filter(|(_, degree)| **degree == 0)
2875 .map(|(path, _)| *path)
2876 .collect();
2877 let mut order: Vec<RulePath> = Vec::with_capacity(edges.len());
2878 while let Some(path) = queue.pop_front() {
2879 for dependent in dependents.get(path).into_iter().flatten() {
2880 let degree = in_degree
2881 .get_mut(dependent)
2882 .expect("BUG: Show rule missing from in_degree");
2883 *degree -= 1;
2884 if *degree == 0 {
2885 queue.push_back(dependent);
2886 }
2887 }
2888 order.push(path.clone());
2889 }
2890 if order.len() != edges.len() {
2891 let unsorted: Vec<&RulePath> =
2892 edges.keys().filter(|path| !order.contains(path)).collect();
2893 let missing = nodes_on_cycles(&unsorted, |path| {
2894 edges
2895 .get(path)
2896 .expect("BUG: unsorted Show rule missing from its edges")
2897 .iter()
2898 .collect()
2899 });
2900 let rules_involved: String = missing
2901 .iter()
2902 .map(|path| path.input_key())
2903 .collect::<Vec<_>>()
2904 .join(", ");
2905 let message = format!("Circular dependency (rules: {rules_involved})");
2906 return Err(missing
2907 .into_iter()
2908 .map(|path| {
2909 let source = match self.rules.get(path) {
2910 Some(node) => node.source.clone(),
2911 None => {
2912 let (identity, rule) = show_imports.rules.get(path).expect(
2913 "BUG: Show rule is neither local nor a reached import rule",
2914 );
2915 self.instance_lists
2916 .templates
2917 .get(identity)
2918 .unwrap_or_else(|| {
2919 panic!(
2920 "BUG: import rule '{path}' has no template of '{identity}'"
2921 )
2922 })
2923 .rules
2924 .get(rule)
2925 .unwrap_or_else(|| {
2926 panic!("BUG: import rule '{path}' missing from its template")
2927 })
2928 .source
2929 .clone()
2930 }
2931 };
2932 Error::validation_with_context(
2933 message.clone(),
2934 Some(source),
2935 None::<String>,
2936 Some(self.main_spec),
2937 None,
2938 )
2939 })
2940 .collect());
2941 }
2942 self.show_imports = show_imports;
2943 self.show_edges = edges;
2944 self.show_order = order;
2945 Ok(())
2946 }
2947
2948 fn topological_sort(&mut self) -> Result<Vec<RulePath>, Vec<Error>> {
2950 let mut depends_on: BTreeMap<RulePath, BTreeSet<RulePath>> = BTreeMap::new();
2951 let local_deps: Vec<(RulePath, Vec<RulePath>)> = self
2952 .rules
2953 .iter()
2954 .map(|(rule_path, rule_node)| {
2955 (
2956 rule_path.clone(),
2957 rule_node.depends_on_rules.iter().cloned().collect(),
2958 )
2959 })
2960 .collect();
2961 for (rule_path, dependencies) in local_deps {
2962 let present = self.present_rule_dependencies(dependencies);
2963 depends_on.insert(rule_path, present);
2964 }
2965 let mut in_degree: BTreeMap<RulePath, usize> = BTreeMap::new();
2966 let mut dependents: BTreeMap<RulePath, Vec<RulePath>> = BTreeMap::new();
2967 let mut queue = VecDeque::new();
2968 let mut result = Vec::new();
2969 for (rule_path, dependencies) in &depends_on {
2970 in_degree.insert(rule_path.clone(), dependencies.len());
2971 for dependency in dependencies {
2972 dependents
2973 .entry(dependency.clone())
2974 .or_default()
2975 .push(rule_path.clone());
2976 }
2977 }
2978
2979 for (rule_path, degree) in &in_degree {
2980 if *degree == 0 {
2981 queue.push_back(rule_path.clone());
2982 }
2983 }
2984
2985 while let Some(rule_path) = queue.pop_front() {
2986 for dependent in dependents.get(&rule_path).into_iter().flatten() {
2987 let degree = in_degree
2988 .get_mut(dependent)
2989 .expect("BUG: rule missing from in_degree");
2990 *degree -= 1;
2991 if *degree == 0 {
2992 queue.push_back(dependent.clone());
2993 }
2994 }
2995 result.push(rule_path);
2996 }
2997
2998 if result.len() != depends_on.len() {
2999 let unsorted: Vec<&RulePath> = depends_on
3000 .keys()
3001 .filter(|rule| !result.contains(rule))
3002 .collect();
3003 let on_cycles = nodes_on_cycles(&unsorted, |rule| {
3004 depends_on
3005 .get(rule)
3006 .expect("BUG: unsorted rule missing from depends_on")
3007 .iter()
3008 .collect()
3009 });
3010 let cycle: Vec<Source> = on_cycles
3011 .iter()
3012 .filter_map(|rule| self.rules.get(*rule).map(|n| n.source.clone()))
3013 .collect();
3014
3015 if cycle.is_empty() {
3016 unreachable!(
3017 "BUG: circular dependency detected but no sources could be collected ({} rules on cycles)",
3018 on_cycles.len()
3019 );
3020 }
3021 let rules_involved: String = on_cycles
3022 .iter()
3023 .map(|rule| rule.input_key())
3024 .collect::<Vec<_>>()
3025 .join(", ");
3026 let message = format!("Circular dependency (rules: {})", rules_involved);
3027 let errors: Vec<Error> = cycle
3028 .into_iter()
3029 .map(|source| {
3030 Error::validation_with_context(
3031 message.clone(),
3032 Some(source),
3033 None::<String>,
3034 Some(self.main_spec),
3035 None,
3036 )
3037 })
3038 .collect();
3039 return Err(errors);
3040 }
3041
3042 Ok(result)
3043 }
3044}
3045
3046#[derive(Debug)]
3047pub(crate) struct RuleNode<'a> {
3048 pub expression: Expression,
3052 pub branches: Vec<(Option<ExprId>, ExprId)>,
3055 pub source: Source,
3056
3057 pub depends_on_rules: BTreeSet<RulePath>,
3058
3059 pub recursive: bool,
3063
3064 pub rule_type: Arc<LemmaType>,
3067
3068 pub scale_families: BTreeSet<TypeFamily>,
3072
3073 pub spec: &'a LemmaSpec,
3075}
3076
3077type ResolvedTypesMap<'a> = Vec<(Arc<LemmaRepository>, &'a LemmaSpec, ResolvedSpecTypes)>;
3078
3079type ParentLookupOk = (
3081 TypeSpecification,
3082 Option<RawSuggestion>,
3083 Option<RawSuggestion>,
3084 Arc<LemmaType>,
3085);
3086
3087type ResolvedTypeEntry = (Arc<LemmaType>, Option<RawSuggestion>, Option<RawSuggestion>);
3089
3090fn local_type_entries(resolved_types: &ResolvedSpecTypes) -> HashMap<String, ResolvedTypeEntry> {
3091 resolved_types
3092 .resolved
3093 .iter()
3094 .map(|(name, lemma_type)| {
3095 (
3096 name.clone(),
3097 (
3098 Arc::clone(lemma_type),
3099 resolved_types.source_defaults.get(name).cloned(),
3100 resolved_types.source_value_defaults.get(name).cloned(),
3101 ),
3102 )
3103 })
3104 .collect()
3105}
3106
3107type RuleReferenceUpdate = (
3109 DataPath,
3110 Arc<LemmaType>,
3111 Option<BoundValueKind>,
3112 Option<BoundValueKind>,
3113);
3114
3115type GraphBuildOk<'a> = (
3117 IndexMap<DataPath, DataDefinition>,
3118 BTreeMap<RulePath, RuleNode<'a>>,
3119 BTreeSet<RulePath>,
3120 Vec<Error>,
3121 ResolvedTypesMap<'a>,
3122 InstanceLists,
3123);
3124
3125struct GraphBuilder<'a, 'i> {
3126 data: IndexMap<DataPath, DataDefinition>,
3127 rules: BTreeMap<RulePath, RuleNode<'a>>,
3128 dropped_rules: BTreeSet<RulePath>,
3129 context: &'a Context,
3130 local_types: ResolvedTypesMap<'a>,
3131 errors: Vec<Error>,
3132 nested_errors: Vec<Error>,
3137 nested_spec_failed: bool,
3138 main_spec: &'a LemmaSpec,
3139 limits: &'a ResourceLimits,
3140 instance_lists: InstanceLists,
3141 interners: &'i mut instance_list::PassInterners,
3142 self_list_row_rules: BTreeSet<RulePath>,
3145}
3146
3147struct RuleExpressionConversion<'a, 'b> {
3148 table: ExprTable<ExpressionKind>,
3150 spec: &'a LemmaSpec,
3151 repository: &'b Arc<LemmaRepository>,
3153 data_map: &'b HashMap<String, &'b LemmaData>,
3154 rule_names: &'b HashSet<&'b str>,
3155 rule: &'b str,
3157 effective: &'b EffectiveDate,
3158 depends_on_rules: &'b mut BTreeSet<RulePath>,
3159 row_scopes: Vec<RowScope>,
3161}
3162
3163impl RuleExpressionConversion<'_, '_> {
3164 fn push(&mut self, kind: ExpressionKind, source: Option<Source>) -> ExprId {
3165 self.table.push(kind, source)
3166 }
3167}
3168
3169type AstRef<'a> = ExprRef<'a, ast::ExpressionKind>;
3170
3171enum Pending<'a> {
3173 Sem(ExprRef<'a, ExpressionKind>),
3174 Ast(AstRef<'a>),
3175 Rule(RulePath),
3176}
3177
3178#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
3180enum SearchKind {
3181 List,
3183 Row,
3185}
3186
3187enum Continuation<'a> {
3189 Answer,
3191 RowOfList,
3194 Member { path: &'a [String], name: &'a str },
3197}
3198
3199struct IdentitySearch<'a> {
3203 kind: SearchKind,
3204 pending: Vec<Pending<'a>>,
3205 visiting: BTreeSet<(SearchKind, RulePath)>,
3207 on_found: Continuation<'a>,
3208}
3209
3210impl<'a> IdentitySearch<'a> {
3211 fn new(kind: SearchKind, first: Pending<'a>, on_found: Continuation<'a>) -> Self {
3212 Self {
3213 kind,
3214 pending: vec![first],
3215 visiting: BTreeSet::new(),
3216 on_found,
3217 }
3218 }
3219
3220 fn nested(&self, kind: SearchKind, first: Pending<'a>, on_found: Continuation<'a>) -> Self {
3221 Self {
3222 kind,
3223 pending: vec![first],
3224 visiting: self.visiting.clone(),
3225 on_found,
3226 }
3227 }
3228
3229 fn enter(&mut self, rule: &RulePath) -> bool {
3231 self.visiting.insert((self.kind, rule.clone()))
3232 }
3233}
3234
3235enum SearchStep<'a> {
3237 Next,
3238 Nested(IdentitySearch<'a>),
3239 Found(SpecIdentity),
3240}
3241
3242fn for_each_conversion_child(kind: &ast::ExpressionKind, mut f: impl FnMut(ExprId)) {
3245 match kind {
3246 ast::ExpressionKind::ListCommand { list, .. } => f(*list),
3247 other => other.for_each_child(f),
3248 }
3249}
3250
3251fn one(children: &[ExprId]) -> ExprId {
3252 let &[child] = children else {
3253 panic!("BUG: AST expression with one operand has one converted child");
3254 };
3255 child
3256}
3257
3258fn convert_chain_steps<Op: Clone>(
3261 steps: &[ast::ChainStep<Op>],
3262 children: &[ExprId],
3263) -> (ExprId, Vec<ast::ChainStep<Op>>) {
3264 let mut children = children.iter().copied();
3265 let first = children
3266 .next()
3267 .expect("BUG: chain expression always has a first operand");
3268 let steps: Vec<ast::ChainStep<Op>> = steps
3269 .iter()
3270 .zip(children.by_ref())
3271 .map(|(step, operand)| ast::ChainStep {
3272 op: step.op.clone(),
3273 operand,
3274 source_location: step.source_location.clone(),
3275 })
3276 .collect();
3277 assert!(
3278 children.next().is_none(),
3279 "BUG: chain converted to more operands than steps"
3280 );
3281 (first, steps)
3282}
3283
3284fn two(children: &[ExprId]) -> (ExprId, ExprId) {
3285 let &[left, right] = children else {
3286 panic!("BUG: AST expression with two operands has two converted children");
3287 };
3288 (left, right)
3289}
3290
3291enum ConvertFrame<'t> {
3295 Enter(AstRef<'t>),
3297 Node(AstRef<'t>),
3299 List(Box<ListCommandBuild<'t>>),
3301 ListArgument(Box<ListCommandBuild<'t>>, ListArgumentSink),
3303 PopRowScope,
3305}
3306
3307struct ConvertScratch<'t> {
3310 frames: Vec<ConvertFrame<'t>>,
3311 values: Vec<Option<ExprId>>,
3312 children: Vec<ExprId>,
3313}
3314
3315enum ListArgumentSink {
3317 Positional,
3319 Field(DataPath),
3321}
3322
3323enum ListArgument {
3325 Enclosing(ExprId),
3327 InRows(ExprId),
3329 Field { name: String, value: ExprId },
3331}
3332
3333struct ListCommandBuild<'t> {
3335 node: AstRef<'t>,
3336 source: Source,
3337 list: ExprId,
3338 row_level: Option<u32>,
3339 row_scope: Option<RowScope>,
3340 pending: VecDeque<ListArgument>,
3341 converted: Vec<ExprId>,
3342 fields: Vec<(DataPath, ExprId)>,
3343 failed: bool,
3344}
3345
3346fn list_command_arguments(
3348 command: &ast::ListCommand,
3349 row_scope: &Option<RowScope>,
3350) -> VecDeque<ListArgument> {
3351 match command {
3352 ast::ListCommand::Find { keys } => keys
3353 .iter()
3354 .map(|key| ListArgument::Enclosing(*key))
3355 .collect(),
3356 ast::ListCommand::First
3357 | ast::ListCommand::Last
3358 | ast::ListCommand::Count
3359 | ast::ListCommand::Key { .. } => VecDeque::new(),
3360 ast::ListCommand::Sum { argument }
3361 | ast::ListCommand::Avg { argument }
3362 | ast::ListCommand::Product { argument }
3363 | ast::ListCommand::Min { argument }
3364 | ast::ListCommand::Max { argument }
3365 | ast::ListCommand::Sort { argument, .. } => argument
3366 .iter()
3367 .map(|argument| ListArgument::InRows(*argument))
3368 .collect(),
3369 ast::ListCommand::Any { form }
3370 | ast::ListCommand::All { form }
3371 | ast::ListCommand::Where { form } => {
3372 let expr = match form {
3373 ast::ListPredicate::Value { expr } | ast::ListPredicate::Instance { expr } => expr,
3374 };
3375 VecDeque::from([ListArgument::InRows(*expr)])
3376 }
3377 ast::ListCommand::Top { n } => VecDeque::from([ListArgument::Enclosing(*n)]),
3378 ast::ListCommand::With { rows } => match (rows, row_scope) {
3379 (ast::ListWithRows::Values(values), None)
3380 | (ast::ListWithRows::Values(values), Some(_)) => values
3381 .iter()
3382 .map(|value| ListArgument::Enclosing(*value))
3383 .collect(),
3384 (ast::ListWithRows::FieldPairs(_), None) => VecDeque::new(),
3385 (ast::ListWithRows::FieldPairs(pairs), Some(_)) => pairs
3386 .iter()
3387 .map(|(field, value)| ListArgument::Field {
3388 name: field.to_string(),
3389 value: *value,
3390 })
3391 .collect(),
3392 },
3393 }
3394}
3395
3396fn optional_argument(
3397 argument: &Option<ExprId>,
3398 converted: &mut impl Iterator<Item = ExprId>,
3399) -> Option<ExprId> {
3400 argument.as_ref().map(|_| next_argument(converted))
3401}
3402
3403fn next_argument(converted: &mut impl Iterator<Item = ExprId>) -> ExprId {
3404 converted
3405 .next()
3406 .expect("BUG: one converted argument per list command argument")
3407}
3408
3409fn list_predicate_op(
3410 form: &ast::ListPredicate,
3411 kind: ListPredKind,
3412 converted: &mut impl Iterator<Item = ExprId>,
3413) -> ListOp {
3414 match form {
3415 ast::ListPredicate::Value { .. } => ListOp::Predicate {
3416 kind,
3417 form: ast::ListPredicateForm::Value,
3418 expr: next_argument(converted),
3419 },
3420 ast::ListPredicate::Instance { .. } => ListOp::Predicate {
3421 kind,
3422 form: ast::ListPredicateForm::Instance,
3423 expr: next_argument(converted),
3424 },
3425 }
3426}
3427
3428struct SourceScope<'s> {
3430 spec: &'s LemmaSpec,
3431}
3432
3433enum RowFieldLookup {
3435 Field {
3438 field: Box<ExpressionKind>,
3439 member: Option<(Vec<String>, String)>,
3440 },
3441 NotARowField,
3442 Failed,
3444}
3445
3446fn row_field_lookup(
3449 level: u32,
3450 identity: &SpecIdentity,
3451 chain: Vec<RowHop>,
3452 parts: &[&str],
3453 qualifier: Option<Vec<String>>,
3454) -> RowFieldLookup {
3455 let first = chain
3456 .into_iter()
3457 .next()
3458 .expect("BUG: a row end chain has a first hop");
3459 assert_eq!(
3460 first.identity, *identity,
3461 "BUG: a row field chain starts on the rows it reads"
3462 );
3463 let member = match parts {
3464 [_] => None,
3465 [_, through @ .., name] => Some((
3466 through.iter().map(|part| (*part).to_string()).collect(),
3467 (*name).to_string(),
3468 )),
3469 [] => unreachable!("BUG: a row field names at least one part"),
3470 };
3471 RowFieldLookup::Field {
3472 field: Box::new(ExpressionKind::RowField {
3473 level,
3474 identity: first.identity,
3475 end: first.end,
3476 qualifier,
3477 }),
3478 member,
3479 }
3480}
3481
3482#[derive(Clone)]
3484enum RowScope {
3485 Value {
3486 level: u32,
3487 list: ExprId,
3488 },
3489 Instance {
3490 list_path: Option<Vec<String>>,
3492 identity: SpecIdentity,
3493 template: RowTemplate,
3494 level: u32,
3495 },
3496}
3497
3498impl RowScope {
3499 fn level(&self) -> u32 {
3500 match self {
3501 Self::Value { level, .. } | Self::Instance { level, .. } => *level,
3502 }
3503 }
3504}
3505
3506struct SpecTypesFrame<'a> {
3509 spec: &'a LemmaSpec,
3510 repository: Arc<LemmaRepository>,
3511 effective: EffectiveDate,
3512 next_data: usize,
3513 pending_targets: VecDeque<discovery::ResolvedDependencyEdge<'a>>,
3515}
3516
3517fn reference_error(main_spec: &LemmaSpec, source: &Source, message: String) -> Error {
3518 Error::validation_with_context(
3519 message,
3520 Some(source.clone()),
3521 None::<String>,
3522 Some(main_spec),
3523 None,
3524 )
3525}
3526
3527fn reference_kind_mismatch_message<P: fmt::Display>(
3545 lhs: &LemmaType,
3546 target_type: &LemmaType,
3547 reference_path: &DataPath,
3548 target_path: &P,
3549 target_kind_label: &str,
3550) -> Option<String> {
3551 if !lhs.has_same_base_type(target_type) {
3552 return Some(format!(
3553 "Data reference '{}' type mismatch: declared as '{}' but {} '{}' is '{}'",
3554 reference_path,
3555 lhs.name(),
3556 target_kind_label,
3557 target_path,
3558 target_type.name(),
3559 ));
3560 }
3561
3562 let lhs_span;
3563 let target_span;
3564 let (lhs_measure, target_measure): (&LemmaType, &LemmaType) = if lhs.is_measure() {
3565 (lhs, target_type)
3566 } else if lhs.is_measure_range() {
3567 lhs_span = range_span_type(lhs);
3568 target_span = range_span_type(target_type);
3569 if lhs_span.is_undetermined() || target_span.is_undetermined() {
3570 unreachable!(
3571 "BUG: MeasureRange reference kinds must yield measure elements via range_span_type"
3572 );
3573 }
3574 (lhs_span.as_ref(), target_span.as_ref())
3575 } else {
3576 return None;
3577 };
3578
3579 if lhs_measure.same_measure_family(target_measure) {
3580 return None;
3581 }
3582 let lhs_family = lhs_measure.measure_family_name().expect(
3583 "BUG: declared measure data must carry a family name; \
3584 anonymous measure types only arise from runtime synthesis \
3585 and never appear as a reference's LHS-declared type",
3586 );
3587 let target_family = target_measure.measure_family_name().expect(
3588 "BUG: declared measure data must carry a family name; \
3589 anonymous measure types only arise from runtime synthesis \
3590 and never appear as a reference target's schema type",
3591 );
3592 Some(format!(
3593 "Data reference '{}' measure family mismatch: declared as '{}' (family '{}') but {} '{}' is '{}' (family '{}')",
3594 reference_path,
3595 lhs.name(),
3596 lhs_family,
3597 target_kind_label,
3598 target_path,
3599 target_type.name(),
3600 target_family,
3601 ))
3602}
3603
3604struct StoredRowKindCheckGraph<'a> {
3607 main_spec: &'a LemmaSpec,
3608 lists: &'a InstanceLists,
3609 data: &'a IndexMap<DataPath, DataDefinition>,
3610 rules: &'a BTreeMap<RulePath, RuleNode<'a>>,
3611 dropped_rules: &'a BTreeSet<RulePath>,
3612 computed_rule_types: &'a HashMap<RulePath, Arc<LemmaType>>,
3613}
3614
3615pub(crate) struct OwnedRow<'r> {
3618 pub row: &'r RowInstance,
3619 pub depth: u32,
3620 pub prefix: Vec<String>,
3621 pub source: &'r Source,
3622}
3623
3624pub(crate) fn for_each_owned_reference<'r>(
3628 data: &'r IndexMap<DataPath, DataDefinition>,
3629 lists: &'r InstanceLists,
3630 mut f: impl FnMut(&OwnedRow<'r>, usize, &'r ReferenceTarget),
3631) {
3632 let mut pending: Vec<OwnedRow<'r>> = Vec::new();
3633 for (path, definition) in data {
3634 if let DataDefinition::Import { row, source, .. } = definition {
3635 pending.push(OwnedRow {
3636 row,
3637 depth: 0,
3638 prefix: vec![path.data.clone()],
3639 source,
3640 });
3641 }
3642 }
3643 for source in lists.sources.values() {
3644 for row in &source.store.rows {
3645 pending.push(written_owned_row(row, 0, &[]));
3646 }
3647 }
3648 pending.reverse();
3649 while let Some(owned) = pending.pop() {
3650 let row = owned.row;
3651 let mut nested_rows: Vec<OwnedRow<'r>> = Vec::new();
3652 for (slot, binding) in row.bindings.iter().enumerate() {
3653 match binding {
3654 None | Some(RowBinding::Bound(_)) => {}
3655 Some(RowBinding::Rows { rows, hops, .. }) => {
3656 let depth = owned
3657 .depth
3658 .checked_sub(*hops)
3659 .expect("BUG: list rows hop above the plan being built");
3660 for nested in rows {
3661 nested_rows.push(written_owned_row(nested, depth, &owned.prefix));
3662 }
3663 }
3664 Some(RowBinding::Instance(nested)) => {
3665 let RowOrigin::Import { alias } = &nested.origin else {
3666 unreachable!("BUG: a nested instance binding holds an import row");
3667 };
3668 let mut prefix = owned.prefix.clone();
3669 prefix.push(alias.to_string());
3670 nested_rows.push(OwnedRow {
3671 row: nested,
3672 depth: owned.depth + 1,
3673 prefix,
3674 source: owned.source,
3675 });
3676 }
3677 Some(RowBinding::Deferred { target, hops }) => {
3678 if *hops == owned.depth {
3679 f(&owned, slot, target);
3680 }
3681 }
3682 }
3683 }
3684 pending.extend(nested_rows.into_iter().rev());
3685 }
3686}
3687
3688fn check_stored_row_reference_types(graph: &StoredRowKindCheckGraph, errors: &mut Vec<Error>) {
3691 for_each_owned_reference(graph.data, graph.lists, |owned, slot, target| {
3692 let template = graph
3693 .lists
3694 .templates
3695 .get(&owned.row.identity)
3696 .expect("BUG: written rows are rows of a planned row spec");
3697 let (field_path, definition) = template
3698 .data
3699 .get_index(slot)
3700 .expect("BUG: row slot is template data");
3701 let display_path = DataPath {
3702 segments: Vec::new(),
3703 data: owned
3704 .prefix
3705 .iter()
3706 .map(String::as_str)
3707 .chain(std::iter::once(field_path.data.as_str()))
3708 .collect::<Vec<_>>()
3709 .join("."),
3710 };
3711 let lhs = definition
3712 .resolved_type_arc()
3713 .expect("BUG: row field is typed template data");
3714 if lhs.is_undetermined() {
3715 return;
3716 }
3717 let Some((target_type, kind_label)) =
3718 reference_target_type(graph, target, &display_path, owned.source, errors)
3719 else {
3720 return;
3721 };
3722 if let Some(msg) = reference_kind_mismatch_message(
3723 lhs,
3724 &target_type,
3725 &display_path,
3726 &match target {
3727 ReferenceTarget::Data(path) => path.to_string(),
3728 ReferenceTarget::Rule(rule) => rule.to_string(),
3729 },
3730 kind_label,
3731 ) {
3732 errors.push(reference_error(graph.main_spec, owned.source, msg));
3733 }
3734 });
3735}
3736
3737fn written_owned_row<'r>(row: &'r RowInstance, depth: u32, holder: &[String]) -> OwnedRow<'r> {
3739 let RowOrigin::Written { instance, source } = &row.origin else {
3740 unreachable!("BUG: stored rows are written rows");
3741 };
3742 let mut prefix = holder.to_vec();
3743 prefix.extend(instance.prefix.iter().map(PathSegment::input_key));
3744 OwnedRow {
3745 row,
3746 depth,
3747 prefix,
3748 source,
3749 }
3750}
3751
3752fn reference_target_type(
3755 graph: &StoredRowKindCheckGraph,
3756 target: &ReferenceTarget,
3757 display_path: &DataPath,
3758 source: &Source,
3759 errors: &mut Vec<Error>,
3760) -> Option<(Arc<LemmaType>, &'static str)> {
3761 let segments = match target {
3762 ReferenceTarget::Data(path) => &path.segments,
3763 ReferenceTarget::Rule(rule) => &rule.segments,
3764 };
3765 if !segments.is_empty() {
3766 let mut identity: Option<&SpecIdentity> = None;
3767 for segment in segments {
3768 let slot = DataPath::local(segment.uses.to_string());
3769 let data = match identity {
3770 None => graph.data,
3771 Some(identity) => &graph.lists.templates[identity].data,
3772 };
3773 let Some(DataDefinition::Import { row, .. }) = data.get(&slot) else {
3774 panic!("BUG: reference target hop '{slot}' of {target:?} is not a `uses` slot");
3775 };
3776 identity = Some(&row.identity);
3777 }
3778 let template = &graph.lists.templates[identity.expect("BUG: a member target has a hop")];
3779 return Some(match target {
3780 ReferenceTarget::Data(path) => (
3781 instance_list::row_data_type(&template.data, &DataPath::local(path.data.clone())),
3782 "target",
3783 ),
3784 ReferenceTarget::Rule(rule) => (
3785 instance_list::row_end_type(
3786 template,
3787 &RowEnd::Rule(RulePath::new(Vec::new(), rule.rule.clone())),
3788 ),
3789 "target rule",
3790 ),
3791 });
3792 }
3793 match target {
3794 ReferenceTarget::Data(target_path) => {
3795 let Some(entry) = graph.data.get(target_path) else {
3796 errors.push(reference_error(
3797 graph.main_spec,
3798 source,
3799 format!(
3800 "Data reference '{}' target '{}' does not exist",
3801 display_path, target_path
3802 ),
3803 ));
3804 return None;
3805 };
3806 match entry {
3807 DataDefinition::TypeDeclaration { resolved_type, .. }
3808 | DataDefinition::Reference { resolved_type, .. }
3809 | DataDefinition::InstanceList { resolved_type, .. }
3810 | DataDefinition::Value { resolved_type, .. } => {
3811 Some((Arc::clone(resolved_type), "target"))
3812 }
3813 DataDefinition::Import { .. } => {
3814 errors.push(reference_error(
3815 graph.main_spec,
3816 source,
3817 format!(
3818 "Data reference '{}' target '{}' is a spec reference and cannot carry a value",
3819 display_path, target_path
3820 ),
3821 ));
3822 None
3823 }
3824 }
3825 }
3826 ReferenceTarget::Rule(target_rule) => {
3827 if let Some(target_type) = graph.computed_rule_types.get(target_rule) {
3828 return Some((Arc::clone(target_type), "target rule"));
3829 }
3830 if graph.rules.contains_key(target_rule) {
3831 panic!("BUG: rule '{target_rule}' type not inferred before row reference check")
3832 }
3833 if !graph.dropped_rules.contains(target_rule) {
3834 errors.push(reference_error(
3835 graph.main_spec,
3836 source,
3837 format!(
3838 "Data reference '{}' target rule '{}' does not exist",
3839 display_path, target_rule
3840 ),
3841 ));
3842 }
3843 None
3844 }
3845 }
3846}
3847
3848fn constraint_application_type_name(parent: &ParentType, data_name: &str) -> String {
3850 let mut current = parent;
3851 loop {
3852 match current {
3853 ParentType::Custom { name, .. } => return name.clone(),
3854 ParentType::Qualified { inner, .. }
3855 | ParentType::Ranged { inner, .. }
3856 | ParentType::List { inner, .. } => current = inner.as_ref(),
3857 ParentType::Primitive { .. } => return data_name.to_string(),
3858 }
3859 }
3860}
3861
3862fn should_defer_ranged_constraints(parent: &ParentType) -> bool {
3866 matches!(parent, ParentType::Ranged { .. })
3867 && matches!(
3868 element_parent_type(parent),
3869 ParentType::Custom { .. } | ParentType::Qualified { .. }
3870 )
3871}
3872
3873struct ConstraintSuggestionSink<'a> {
3878 suggestion: &'a mut Option<RawSuggestion>,
3879 value: &'a mut Option<RawSuggestion>,
3880 resolve_unit: Option<&'a UnitTokenResolver<'a>>,
3881}
3882
3883fn apply_constraints_to_spec(
3884 spec: &LemmaSpec,
3885 type_name: &str,
3886 data_name: &str,
3887 lemma_type: &mut LemmaType,
3888 constraints: &[Constraint],
3889 source: &Source,
3890 sink: ConstraintSuggestionSink<'_>,
3891) -> Result<(), Vec<Error>> {
3892 let mut errors = Vec::new();
3893
3894 let mut seen_unit_names: std::collections::HashSet<String> = Default::default();
3895 let mut seen_singleton_commands: std::collections::HashSet<TypeConstraintCommand> =
3896 Default::default();
3897 let mut has_suggest = false;
3898 let mut has_value = false;
3899 for row in constraints.iter() {
3900 let command = row.command;
3901 let args = &row.args;
3902 if command == TypeConstraintCommand::Suggest {
3903 has_suggest = true;
3904 }
3905 if command == TypeConstraintCommand::With {
3906 has_value = true;
3907 }
3908 if command == TypeConstraintCommand::Unit {
3909 if let Some(CommandArg::Label(name)) = args.first() {
3910 if !seen_unit_names.insert(name.clone()) {
3911 errors.push(Error::validation_with_context(
3912 format!(
3913 "Duplicate unit '{}': each unit name may appear at most once per type definition",
3914 name
3915 ),
3916 Some(source.clone()),
3917 None::<String>,
3918 Some(spec),
3919 None,
3920 ));
3921 }
3922 }
3923 }
3924 if matches!(
3925 command,
3926 TypeConstraintCommand::Minimum
3927 | TypeConstraintCommand::Maximum
3928 | TypeConstraintCommand::Decimals
3929 | TypeConstraintCommand::Suggest
3930 | TypeConstraintCommand::With
3931 ) && !(command == TypeConstraintCommand::With
3932 && matches!(lemma_type.specifications, TypeSpecification::List { .. }))
3933 && !seen_singleton_commands.insert(command)
3934 {
3935 errors.push(Error::validation_with_context(
3936 format!(
3937 "Duplicate '{command}' constraint: each may appear at most once per type declaration"
3938 ),
3939 Some(source.clone()),
3940 None::<String>,
3941 Some(spec),
3942 None,
3943 ));
3944 }
3945 }
3946 if has_suggest && has_value {
3947 errors.push(Error::validation_with_context(
3948 "Cannot use both 'with' and 'suggest' on the same data declaration".to_string(),
3949 Some(source.clone()),
3950 None::<String>,
3951 Some(spec),
3952 None,
3953 ));
3954 }
3955 if !errors.is_empty() {
3956 return Err(errors);
3957 }
3958
3959 let ConstraintSuggestionSink {
3960 suggestion: declared_suggestion,
3961 value: declared_value,
3962 resolve_unit,
3963 } = sink;
3964 let names = ConstraintNames {
3965 type_name,
3966 data_name,
3967 };
3968
3969 let mut apply_one = |lemma_type: &mut LemmaType,
3970 command: TypeConstraintCommand,
3971 args: &[CommandArg],
3972 declared_suggestion: &mut Option<RawSuggestion>,
3973 declared_value: &mut Option<RawSuggestion>| {
3974 let mut suggestion_before = declared_suggestion.clone();
3975 let mut value_before = declared_value.clone();
3976 let applied = if matches!(
3977 command,
3978 TypeConstraintCommand::Option | TypeConstraintCommand::Options
3979 ) {
3980 apply_option_constraint(lemma_type, command, args, resolve_unit, spec)
3981 } else if matches!(
3982 command,
3983 TypeConstraintCommand::Suggest | TypeConstraintCommand::With
3984 ) && matches!(lemma_type.specifications, TypeSpecification::Spec { .. })
3985 {
3986 let target = match command {
3987 TypeConstraintCommand::Suggest => &mut suggestion_before,
3988 TypeConstraintCommand::With => &mut value_before,
3989 _ => unreachable!("BUG: only Suggest or With in this arm"),
3990 };
3991 apply_spec_member_command(lemma_type, command, args, spec, target)
3992 } else if matches!(lemma_type.specifications, TypeSpecification::List { .. })
3993 && command == TypeConstraintCommand::With
3994 {
3995 apply_list_with_rows(
3996 lemma_type,
3997 names,
3998 args,
3999 &mut value_before,
4000 resolve_unit,
4001 spec,
4002 )
4003 } else {
4004 lemma_type.apply_constraint(
4005 names,
4006 command,
4007 args,
4008 &mut suggestion_before,
4009 &mut value_before,
4010 resolve_unit,
4011 )
4012 };
4013 match applied {
4014 Ok(()) => {
4015 *declared_suggestion = suggestion_before;
4016 *declared_value = value_before;
4017 }
4018 Err(e) => {
4019 errors.push(Error::validation_with_context(
4020 format!("Failed to apply constraint '{}': {}", command, e),
4021 Some(source.clone()),
4022 None::<String>,
4023 Some(spec),
4024 None,
4025 ));
4026 }
4027 }
4028 };
4029
4030 for row in constraints {
4031 let command = row.command;
4032 let args = &row.args;
4033 if matches!(
4034 command,
4035 TypeConstraintCommand::Unit | TypeConstraintCommand::Trait
4036 ) {
4037 apply_one(
4038 lemma_type,
4039 command,
4040 args,
4041 declared_suggestion,
4042 declared_value,
4043 );
4044 }
4045 }
4046 for row in constraints {
4047 let command = row.command;
4048 let args = &row.args;
4049 if !matches!(
4050 command,
4051 TypeConstraintCommand::Unit | TypeConstraintCommand::Trait
4052 ) {
4053 apply_one(
4054 lemma_type,
4055 command,
4056 args,
4057 declared_suggestion,
4058 declared_value,
4059 );
4060 }
4061 }
4062 if !errors.is_empty() {
4063 return Err(errors);
4064 }
4065 Ok(())
4066}
4067
4068fn expression_has_source_locations(expr: &ast::Expression) -> bool {
4070 expr.table
4071 .nodes()
4072 .all(|node| node.source_location.is_some())
4073}
4074
4075fn validate_ast_source_locations(
4080 ordered_dependencies: &[discovery::DependencySpec<'_>],
4081) -> Vec<Error> {
4082 let mut errors = Vec::new();
4083 for node in ordered_dependencies {
4084 let spec = node.spec;
4085 for rule in &spec.rules {
4086 if !expression_has_source_locations(&rule.expression) {
4087 errors.push(Error::validation(
4088 format!(
4089 "In spec '{}': rule '{}' contains an expression without a source location; \
4090 programmatically constructed ASTs must set source_location on every expression",
4091 spec.name, rule.name
4092 ),
4093 Some(rule.source_location.clone()),
4094 None::<String>,
4095 ));
4096 }
4097 for clause in &rule.unless_clauses {
4098 if !expression_has_source_locations(&clause.condition)
4099 || !expression_has_source_locations(&clause.result)
4100 {
4101 errors.push(Error::validation(
4102 format!(
4103 "In spec '{}': rule '{}' has an unless clause with an expression without \
4104 a source location; programmatically constructed ASTs must set \
4105 source_location on every expression",
4106 spec.name, rule.name
4107 ),
4108 Some(clause.source_location.clone()),
4109 None::<String>,
4110 ));
4111 }
4112 }
4113 }
4114 }
4115 errors
4116}
4117
4118impl<'a> Graph<'a> {
4119 pub(crate) fn build(
4121 context: &'a Context,
4122 repository: &Arc<LemmaRepository>,
4123 main_spec: &'a LemmaSpec,
4124 ordered_dependencies: &[discovery::DependencySpec<'a>],
4125 effective: &EffectiveDate,
4126 limits: &'a ResourceLimits,
4127 interners: &mut instance_list::PassInterners,
4128 ) -> Result<(Graph<'a>, ResolvedSpecTypes), Vec<Error>> {
4129 let boundary_errors = validate_ast_source_locations(ordered_dependencies);
4130 if !boundary_errors.is_empty() {
4131 return Err(boundary_errors);
4132 }
4133
4134 let mut errors: Vec<Error> = Vec::new();
4135
4136 let mut slices = HashMap::new();
4137 for node in ordered_dependencies {
4138 match interners
4139 .slices
4140 .of(context, &node.repository, node.spec, limits)
4141 {
4142 Ok(version_slices) => {
4143 slices.insert(
4144 SpecVersionKey::of(&node.repository, node.spec),
4145 version_slices,
4146 );
4147 }
4148 Err(slice_errors) => errors.extend(slice_errors),
4149 }
4150 }
4151 if !errors.is_empty() {
4152 return Err(errors);
4153 }
4154
4155 let mut type_resolver = TypeResolver::new(context, slices);
4156 for node in ordered_dependencies {
4157 errors.extend(type_resolver.register_all(&node.repository, node.spec));
4158 }
4159
4160 let (data, rules, dropped_rules, graph_errors, local_types, instance_lists) =
4161 match GraphBuilder::build(
4162 context,
4163 repository,
4164 main_spec,
4165 effective,
4166 &type_resolver,
4167 limits,
4168 interners,
4169 ) {
4170 Ok(built) => built,
4171 Err(build_errors) => {
4172 errors.extend(build_errors);
4173 return Err(errors);
4174 }
4175 };
4176
4177 let mut graph = Graph {
4178 data,
4179 rules,
4180 dropped_rules,
4181 rule_order: Vec::new(),
4182 data_reference_order: Vec::new(),
4183 reference_ends: IndexMap::new(),
4184 rule_candidates: HashMap::new(),
4185 main_spec,
4186 instance_lists,
4187 show_imports: ShowImports::default(),
4188 show_edges: BTreeMap::new(),
4189 show_order: Vec::new(),
4190 unit_scale_targets: IndexMap::new(),
4191 };
4192
4193 let validation_errors = match graph.validate(&local_types, limits) {
4194 Ok(()) => Vec::new(),
4195 Err(errors) => errors,
4196 };
4197
4198 errors.extend(graph_errors);
4199 errors.extend(validation_errors);
4200
4201 if errors.is_empty() {
4202 let main_resolved_types = local_types
4203 .into_iter()
4204 .find_map(|(_, spec, types)| {
4205 discovery::same_loaded_spec(spec, main_spec).then_some(types)
4206 })
4207 .expect("BUG: main spec missing from local_types");
4208 Ok((graph, main_resolved_types))
4209 } else {
4210 Err(errors)
4211 }
4212 }
4213
4214 fn validate(
4215 &mut self,
4216 resolved_types: &ResolvedTypesMap,
4217 limits: &ResourceLimits,
4218 ) -> Result<(), Vec<Error>> {
4219 let mut errors = Vec::new();
4220
4221 if let Err(structural_errors) = check_all_rule_references_exist(self) {
4223 errors.extend(structural_errors);
4224 }
4225 if let Err(depth_errors) = instance_list::check_instance_list_depths(
4226 &self.instance_lists,
4227 &self.data,
4228 limits.max_list_depth,
4229 ) {
4230 errors.extend(depth_errors);
4231 }
4232 if let Err(collision_errors) = check_data_and_rule_name_collisions(self) {
4233 errors.extend(collision_errors);
4234 }
4235
4236 let reference_order = match self.compute_data_reference_order() {
4242 Ok(order) => order,
4243 Err(circular_errors) => {
4244 errors.extend(circular_errors);
4245 return Err(errors);
4246 }
4247 };
4248 if let Err(circular_errors) = self.check_binding_reference_cycles() {
4249 errors.extend(circular_errors);
4250 return Err(errors);
4251 }
4252
4253 if let Err(reference_errors) =
4257 self.resolve_data_reference_types(&reference_order, resolved_types)
4258 {
4259 errors.extend(reference_errors);
4260 }
4261
4262 self.reference_ends = self.compute_reference_ends();
4265
4266 self.add_rule_reference_dependency_edges();
4272 self.add_options_rule_dependency_edges();
4273 self.add_dynamic_import_pin_dependency_edges();
4274
4275 match self.link_spec_values() {
4276 Ok(member_errors) => errors.extend(member_errors),
4277 Err(cycle_errors) => {
4278 errors.extend(cycle_errors);
4279 return Err(errors);
4280 }
4281 }
4282
4283 self.add_stored_row_dependency_edges();
4289
4290 if let Err(circular_errors) = self.sort_show_rules() {
4291 errors.extend(circular_errors);
4292 return Err(errors);
4293 }
4294
4295 let rule_order = match self.topological_sort() {
4296 Ok(order) => order,
4297 Err(circular_errors) => {
4298 errors.extend(circular_errors);
4299 return Err(errors);
4300 }
4301 };
4302
4303 let has_rule_references = self.data.values().any(|def| {
4309 matches!(
4310 def,
4311 DataDefinition::Reference {
4312 target: ReferenceTarget::Rule(_),
4313 ..
4314 }
4315 )
4316 });
4317 let mut infer_cache = InferExpressionTypeCache::new();
4318 let (inferred_types, seed_errors) =
4319 infer_rule_types(self, &rule_order, resolved_types, &mut infer_cache);
4320 let rule_reference_errors =
4321 self.resolve_rule_reference_types(&inferred_types, resolved_types);
4322 let row_store_reference_errors = self.check_row_store_reference_types(&inferred_types);
4323 if has_rule_references {
4324 infer_cache.clear();
4325 }
4326 let type_errors = check_rule_types(
4327 self,
4328 &rule_order,
4329 &inferred_types,
4330 resolved_types,
4331 &mut infer_cache,
4332 );
4333
4334 errors.extend(seed_errors);
4335 if let Err(errs) = rule_reference_errors {
4336 errors.extend(errs);
4337 }
4338 if let Err(errs) = row_store_reference_errors {
4339 errors.extend(errs);
4340 }
4341 if let Err(errs) = type_errors {
4342 errors.extend(errs);
4343 }
4344 errors.extend(deferred_signature_errors(
4345 self,
4346 resolved_types,
4347 &inferred_types,
4348 ));
4349 errors.extend(self.check_unit_scale_target_types(resolved_types, &inferred_types));
4350
4351 if !errors.is_empty() {
4352 return Err(errors);
4353 }
4354
4355 let scale_errors = self.plan_unit_scales(resolved_types, &inferred_types, &mut infer_cache);
4358 if !scale_errors.is_empty() {
4359 return Err(scale_errors);
4360 }
4361 let rule_order = self.topological_sort()?;
4362
4363 errors.extend(self.validate_dynamic_import_rule_pins(&inferred_types));
4364 if !errors.is_empty() {
4365 return Err(errors);
4366 }
4367
4368 apply_inferred_types(self, inferred_types);
4370 self.rule_order = rule_order;
4371 self.data_reference_order = reference_order;
4372 Ok(())
4373 }
4374
4375 fn validate_dynamic_import_rule_pins(
4377 &self,
4378 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
4379 ) -> Vec<Error> {
4380 let mut errors = Vec::new();
4381 for definition in self.data.values() {
4382 let DataDefinition::Import {
4383 pin: Some(ImportEffectivePin::Rule(path)),
4384 source,
4385 ..
4386 } = definition
4387 else {
4388 continue;
4389 };
4390 let Some(lemma_type) = inferred_types.get(path) else {
4391 errors.push(Error::validation_with_context(
4392 format!(
4393 "`-> effective {}` does not name a rule of this spec",
4394 path.rule
4395 ),
4396 Some(source.clone()),
4397 None::<String>,
4398 Some(self.main_spec),
4399 None,
4400 ));
4401 continue;
4402 };
4403 if !lemma_type.is_date() {
4404 errors.push(Error::validation_with_context(
4405 format!(
4406 "`-> effective` requires a date or datetime, got {}",
4407 lemma_type.specifications
4408 ),
4409 Some(source.clone()),
4410 None::<String>,
4411 Some(self.main_spec),
4412 None,
4413 ));
4414 }
4415 }
4416 errors
4417 }
4418}
4419
4420fn local_data_field_names(spec: &LemmaSpec) -> Vec<String> {
4421 spec.data
4422 .iter()
4423 .filter(|d| d.reference.is_local())
4424 .map(|d| d.reference.name.clone())
4425 .collect()
4426}
4427
4428fn is_uses_vs_data_clash(existing: &DataDefinition, incoming: &ParsedDataValue) -> bool {
4429 matches!(
4430 (existing, incoming),
4431 (
4432 DataDefinition::Import { .. },
4433 ParsedDataValue::Definition { .. }
4434 )
4435 ) || matches!(
4436 (existing, incoming),
4437 (
4438 DataDefinition::TypeDeclaration { .. } | DataDefinition::Value { .. },
4439 ParsedDataValue::Import { .. }
4440 )
4441 )
4442}
4443
4444fn qualified_type_name_from_definition(incoming: &ParsedDataValue) -> Option<&str> {
4445 let ParsedDataValue::Definition {
4446 base: Some(ParentType::Qualified { inner, .. }),
4447 ..
4448 } = incoming
4449 else {
4450 return None;
4451 };
4452 match inner.as_ref() {
4453 ParentType::Custom { name, .. } => Some(name.as_str()),
4454 _ => None,
4455 }
4456}
4457
4458fn import_spec_ref_for_clash<'a>(
4459 consumer_spec: &'a LemmaSpec,
4460 incoming: &'a LemmaData,
4461 existing: &DataDefinition,
4462) -> std::borrow::Cow<'a, ast::SpecRef> {
4463 if let ParsedDataValue::Import { spec_ref, .. } = &incoming.value {
4464 return std::borrow::Cow::Borrowed(spec_ref);
4465 }
4466 let alias = &incoming.reference.name;
4467 for row in &consumer_spec.data {
4468 if !row.reference.is_local() || row.reference.name != *alias {
4469 continue;
4470 }
4471 let ParsedDataValue::Import { spec_ref, .. } = &row.value else {
4472 continue;
4473 };
4474 return std::borrow::Cow::Borrowed(spec_ref);
4475 }
4476 let DataDefinition::Import { row, .. } = existing else {
4477 unreachable!("BUG: uses vs data clash but no Import side with recoverable spec_ref");
4478 };
4479 std::borrow::Cow::Owned(ast::SpecRef::same_repository(row.identity.spec.clone()))
4480}
4481
4482fn uses_vs_data_duplicate_message(
4483 consumer_spec: &LemmaSpec,
4484 incoming: &LemmaData,
4485 existing: &DataDefinition,
4486) -> (String, Option<String>) {
4487 match (existing, &incoming.value) {
4488 (DataDefinition::Import { .. }, ParsedDataValue::Definition { .. })
4489 | (
4490 DataDefinition::TypeDeclaration { .. } | DataDefinition::Value { .. },
4491 ParsedDataValue::Import { .. },
4492 ) => {}
4493 _ => unreachable!("uses_vs_data_duplicate_message requires a uses vs data clash"),
4494 };
4495 let alias = &incoming.reference.name;
4496 let spec_ref = import_spec_ref_for_clash(consumer_spec, incoming, existing);
4497 let target_label = spec_ref.to_string();
4498 let import_alias = format!("{alias}_spec");
4499 let message = format!(
4500 "You used the name `{alias}` for both a `uses` import (target {target_label}) and a `data` definition.",
4501 );
4502 let suggestion = match qualified_type_name_from_definition(&incoming.value) {
4503 Some(type_name) => format!(
4504 "Rename the `uses` import alias to `{import_alias}`. The qualified type path must use `{import_alias}.{type_name}`."
4505 ),
4506 _ => format!("Rename the `uses` import alias to `{import_alias}`."),
4507 };
4508 (message, Some(suggestion))
4509}
4510
4511impl<'a, 'i> GraphBuilder<'a, 'i> {
4512 fn build(
4513 context: &'a Context,
4514 repository: &Arc<LemmaRepository>,
4515 main_spec: &'a LemmaSpec,
4516 effective: &EffectiveDate,
4517 type_resolver: &TypeResolver<'a>,
4518 limits: &'a ResourceLimits,
4519 interners: &'i mut instance_list::PassInterners,
4520 ) -> Result<GraphBuildOk<'a>, Vec<Error>> {
4521 let mut builder = GraphBuilder {
4522 data: IndexMap::new(),
4523 rules: BTreeMap::new(),
4524 dropped_rules: BTreeSet::new(),
4525 context,
4526 local_types: Vec::new(),
4527 errors: Vec::new(),
4528 nested_errors: Vec::new(),
4529 nested_spec_failed: false,
4530 main_spec,
4531 limits,
4532 instance_lists: InstanceLists::new(SpecIdentity::of(
4533 repository,
4534 main_spec,
4535 effective.clone(),
4536 )),
4537 interners,
4538 self_list_row_rules: BTreeSet::new(),
4539 };
4540
4541 builder.build_spec(main_spec, repository, effective, type_resolver);
4542
4543 if builder.nested_spec_failed {
4544 return Err(builder.nested_errors);
4545 }
4546
4547 builder.resolve_unit_scales();
4548 builder.mark_self_list_row_rules_recursive();
4549
4550 Ok((
4551 builder.data,
4552 builder.rules,
4553 builder.dropped_rules,
4554 builder.errors,
4555 builder.local_types,
4556 builder.instance_lists,
4557 ))
4558 }
4559
4560 fn engine_error(&self, message: impl Into<String>, source: &Source) -> Error {
4561 Error::validation_with_context(
4562 message.into(),
4563 Some(source.clone()),
4564 None::<String>,
4565 Some(self.main_spec),
4566 None,
4567 )
4568 }
4569
4570 fn resolve_unit_ref(
4571 &self,
4572 current_spec: &LemmaSpec,
4573 unit_ref: &str,
4574 ) -> Result<(String, Arc<LemmaType>), String> {
4575 let resolved = find_types_by_spec(&self.local_types, current_spec)
4576 .ok_or_else(|| {
4577 format!(
4578 "Unknown unit '{unit_ref}'. Declare it on a measure or ratio type, or import it with uses."
4579 )
4580 })?;
4581 resolved
4582 .unit_index
4583 .resolve_with_named_types(unit_ref, &resolved.resolved)
4584 }
4585
4586 fn measure_ratio_range_from_number_with_unit_endpoints(
4589 &self,
4590 current_spec: &LemmaSpec,
4591 left_mag: rust_decimal::Decimal,
4592 left_unit: &str,
4593 right_mag: rust_decimal::Decimal,
4594 right_unit: &str,
4595 ) -> Result<ValueKind, String> {
4596 let (left_bare, left_lt) = self.resolve_unit_ref(current_spec, left_unit)?;
4597 let (right_bare, right_lt) = self.resolve_unit_ref(current_spec, right_unit)?;
4598 let left_kind = number_with_unit_to_value_kind(left_mag, &left_bare, left_lt.as_ref())?;
4599 let right_kind = number_with_unit_to_value_kind(right_mag, &right_bare, right_lt.as_ref())?;
4600 let left_ty = Arc::new(left_lt.as_ref().clone().with_unit(left_bare.as_str()));
4601 let right_ty = Arc::new(right_lt.as_ref().clone().with_unit(right_bare.as_str()));
4602 range_type_specification_from_endpoints(left_ty.as_ref(), right_ty.as_ref()).ok_or_else(
4603 || {
4604 format!(
4605 "Range endpoints must share a measure or ratio family (got '{}' and '{}')",
4606 left_lt.name(),
4607 right_lt.name()
4608 )
4609 },
4610 )?;
4611 Ok(ValueKind::Range(
4612 Box::new(TypedLiteral {
4613 value: left_kind,
4614 lemma_type: left_ty,
4615 }),
4616 Box::new(TypedLiteral {
4617 value: right_kind,
4618 lemma_type: right_ty,
4619 }),
4620 ))
4621 }
4622
4623 fn resolve_unit_scales(&mut self) {
4628 let spec = self.main_spec;
4629 let Some(spec_idx) = self
4630 .local_types
4631 .iter()
4632 .position(|(_, registered, _)| discovery::same_loaded_spec(registered, spec))
4633 else {
4634 return;
4635 };
4636 let type_names: Vec<String> = self.local_types[spec_idx]
4637 .2
4638 .resolved
4639 .keys()
4640 .cloned()
4641 .collect();
4642 for type_name in type_names {
4643 self.resolve_type_unit_scales(spec_idx, &type_name);
4644 }
4645 }
4646
4647 fn resolve_type_unit_scales(&mut self, spec_idx: usize, type_name: &str) {
4648 let lemma_type = Arc::clone(
4649 self.local_types[spec_idx]
4650 .2
4651 .resolved
4652 .get(type_name)
4653 .expect("BUG: type_name comes from resolved keys"),
4654 );
4655 let TypeSpecification::Measure { units, .. } = &lemma_type.specifications else {
4656 return;
4657 };
4658 let dynamic: Vec<(usize, Vec<String>, String)> = units
4659 .iter()
4660 .enumerate()
4661 .filter_map(|(idx, unit)| {
4662 let (segments, name) = unit.scale_reference_parts()?;
4663 if unit.resolved_scale_target().is_some() {
4664 return None;
4665 }
4666 Some((idx, segments.to_vec(), name.to_string()))
4667 })
4668 .collect();
4669 if dynamic.is_empty() {
4670 return;
4671 }
4672 let spec = self.local_types[spec_idx].1;
4673 let source = spec
4674 .data
4675 .iter()
4676 .find(|data| data.reference.is_local() && data.reference.name == type_name)
4677 .map(|data| data.source_location.clone())
4678 .or_else(|| spec.data.first().map(|data| data.source_location.clone()))
4679 .or_else(|| spec.rules.first().map(|rule| rule.source_location.clone()))
4680 .expect("BUG: a spec that declares a measure type has a source location");
4681 let family_spec = lemma_type
4682 .measure_family()
4683 .map(|family| family.spec.as_str())
4684 .unwrap_or(spec.name.as_str());
4685 let mut resolved_targets: Vec<(usize, ReferenceTarget)> = Vec::new();
4686 for (idx, segments, name) in dynamic {
4687 let reference = ast::Reference { segments, name };
4688 let result = if family_spec == spec.name {
4689 self.resolve_scale_reference(spec, &reference, &source)
4690 } else {
4691 self.resolve_imported_scale_reference(family_spec, &reference, &source)
4692 };
4693 match result {
4694 Ok(target) => resolved_targets.push((idx, target)),
4695 Err(error) => self.errors.push(error),
4696 }
4697 }
4698 if resolved_targets.is_empty() {
4699 return;
4700 }
4701 let mut owned = lemma_type.as_ref().clone();
4702 let TypeSpecification::Measure { units, .. } = &mut owned.specifications else {
4703 unreachable!("BUG: measure type still a measure after clone");
4704 };
4705 for (idx, target) in resolved_targets {
4706 units.0[idx] = units.0[idx].clone().with_resolved_scale(target);
4707 }
4708 self.local_types[spec_idx]
4709 .2
4710 .resolved
4711 .insert(type_name.to_string(), Arc::new(owned));
4712 }
4713
4714 fn resolve_imported_scale_reference(
4715 &self,
4716 family_spec: &str,
4717 reference: &ast::Reference,
4718 source: &Source,
4719 ) -> Result<ReferenceTarget, Error> {
4720 let Some(template) = self
4721 .instance_lists
4722 .templates
4723 .values()
4724 .find(|plan| plan.spec_name == family_spec)
4725 else {
4726 return Err(self.engine_error(
4727 format!(
4728 "Reference target '{}' not found in spec '{family_spec}'",
4729 reference.name
4730 ),
4731 source,
4732 ));
4733 };
4734 if !reference.segments.is_empty() {
4735 return Err(self.engine_error(
4736 format!(
4737 "unit scale reference '{reference}' on an imported measure must be local to '{family_spec}'"
4738 ),
4739 source,
4740 ));
4741 }
4742 member_in_template(template, &reference.name, |message| {
4743 self.engine_error(message, source)
4744 })
4745 }
4746
4747 fn resolve_scale_reference(
4748 &self,
4749 spec: &LemmaSpec,
4750 reference: &ast::Reference,
4751 source: &Source,
4752 ) -> Result<ReferenceTarget, Error> {
4753 if reference.segments.is_empty() {
4754 let is_data = spec
4755 .data
4756 .iter()
4757 .any(|d| d.reference.is_local() && d.reference.name == reference.name);
4758 let is_rule = spec.rules.iter().any(|r| r.name == reference.name);
4759 if is_data && is_rule {
4760 return Err(self.engine_error(
4761 format!(
4762 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
4763 reference.name, spec.name
4764 ),
4765 source,
4766 ));
4767 }
4768 if is_data {
4769 let is_import = spec.data.iter().any(|d| {
4770 d.reference.is_local()
4771 && d.reference.name == reference.name
4772 && matches!(d.value, ParsedDataValue::Import { .. })
4773 });
4774 if is_import {
4775 return Err(self.engine_error(
4776 format!(
4777 "cannot supply spec instance '{}' as a value",
4778 reference.name
4779 ),
4780 source,
4781 ));
4782 }
4783 return Ok(ReferenceTarget::Data(DataPath::local(
4784 reference.name.clone(),
4785 )));
4786 }
4787 if is_rule {
4788 return Ok(ReferenceTarget::Rule(RulePath::new(
4789 Vec::new(),
4790 reference.name.clone(),
4791 )));
4792 }
4793 return Err(self.engine_error(
4794 format!(
4795 "Reference target '{}' not found in spec '{}'",
4796 reference.name, spec.name
4797 ),
4798 source,
4799 ));
4800 }
4801
4802 let [alias] = reference.segments.as_slice() else {
4803 return Err(self.engine_error(
4804 format!(
4805 "unit scale reference '{reference}' is not a data or rule path in spec '{}'",
4806 spec.name
4807 ),
4808 source,
4809 ));
4810 };
4811 let Some(DataDefinition::Import { row, .. }) =
4812 self.data.get(&DataPath::local(alias.clone()))
4813 else {
4814 return Err(self.engine_error(
4815 format!(
4816 "Reference target '{}' not found in spec '{}'",
4817 reference, spec.name
4818 ),
4819 source,
4820 ));
4821 };
4822 let template = self
4823 .instance_lists
4824 .templates
4825 .get(row.identity.as_ref())
4826 .unwrap_or_else(|| {
4827 panic!(
4828 "BUG: import '{alias}' has no template plan for '{}'",
4829 row.identity.spec
4830 )
4831 });
4832 let hop = PathSegment::import(alias, row.identity.as_ref());
4833 match member_in_template(template, &reference.name, |message| {
4834 self.engine_error(message, source)
4835 })? {
4836 ReferenceTarget::Data(path) => {
4837 let mut segments = vec![hop];
4838 segments.extend(path.segments);
4839 Ok(ReferenceTarget::Data(DataPath {
4840 segments,
4841 data: path.data,
4842 }))
4843 }
4844 ReferenceTarget::Rule(path) => {
4845 let mut segments = vec![hop];
4846 segments.extend(path.segments);
4847 Ok(ReferenceTarget::Rule(RulePath {
4848 segments,
4849 rule: path.rule,
4850 }))
4851 }
4852 }
4853 }
4854
4855 fn process_meta_fields(&mut self, spec: &LemmaSpec) {
4856 let mut seen = HashSet::new();
4857 for field in &spec.meta_fields {
4858 if !seen.insert(field.key.clone()) {
4859 self.errors.push(self.engine_error(
4860 format!("Duplicate meta key '{}'", field.key),
4861 &field.source_location,
4862 ));
4863 }
4864 }
4865 }
4866
4867 fn resolve_spec_ref(
4868 &self,
4869 spec_ref: &ast::SpecRef,
4870 effective: &EffectiveDate,
4871 consumer_spec: &LemmaSpec,
4872 consumer_repository: &Arc<LemmaRepository>,
4873 ) -> Result<(Arc<LemmaRepository>, &'a LemmaSpec), Error> {
4874 discovery::resolve_spec_ref(
4875 self.context,
4876 spec_ref,
4877 consumer_repository,
4878 consumer_spec,
4879 effective,
4880 None,
4881 )
4882 }
4883
4884 fn push_missing_with_data_field_error(
4885 &mut self,
4886 import_row: &LemmaData,
4887 binding: &ast::UsesBinding,
4888 walk_spec: &LemmaSpec,
4889 field_name: &str,
4890 ) {
4891 let ParsedDataValue::Import { spec_ref, .. } = &import_row.value else {
4892 unreachable!("BUG: push_missing_with_data_field_error called on non-Import LemmaData");
4893 };
4894 let import_alias = &import_row.reference.name;
4895 let target_label = spec_ref.to_string();
4896 let did_you_mean = closest_name(field_name, &local_data_field_names(walk_spec));
4897 let message = format!("`{}` has no data field `{}`", walk_spec.name, field_name);
4898 let mut suggestion = format!(
4899 "`-> with` on import `{import_alias}` (target {target_label}) must name a data field on `{spec}`",
4900 spec = walk_spec.name,
4901 );
4902 if let Some(name) = did_you_mean {
4903 suggestion = format!("{suggestion}. Did you mean `{name}`?");
4904 }
4905 self.errors.push(Error::validation_with_context(
4906 message,
4907 Some(binding.source_location.clone()),
4908 Some(suggestion),
4909 Some(self.main_spec),
4910 None,
4911 ));
4912 }
4913
4914 fn resolve_reference_target_in_spec(
4921 &mut self,
4922 reference: &ast::Reference,
4923 reference_source: &Source,
4924 containing_spec: &'a LemmaSpec,
4925 containing_repository: &Arc<LemmaRepository>,
4926 effective: &EffectiveDate,
4927 ) -> Option<ReferenceTarget> {
4928 if reference.segments.is_empty() {
4929 let data = containing_spec
4930 .data
4931 .iter()
4932 .find(|d| d.reference.is_local() && d.reference.name == reference.name);
4933 let is_rule = containing_spec
4934 .rules
4935 .iter()
4936 .any(|r| r.name == reference.name);
4937 if data.is_some() && is_rule {
4938 self.errors.push(self.engine_error(
4939 format!(
4940 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
4941 reference.name, containing_spec.name
4942 ),
4943 reference_source,
4944 ));
4945 return None;
4946 }
4947 if let Some(data) = data {
4948 if matches!(data.value, ParsedDataValue::Import { .. }) {
4949 self.errors.push(self.engine_error(
4950 format!(
4951 "cannot supply spec instance '{}' as a value",
4952 reference.name
4953 ),
4954 reference_source,
4955 ));
4956 return None;
4957 }
4958 return Some(ReferenceTarget::Data(DataPath::local(
4959 reference.name.clone(),
4960 )));
4961 }
4962 if is_rule {
4963 return Some(ReferenceTarget::Rule(RulePath::new(
4964 Vec::new(),
4965 reference.name.clone(),
4966 )));
4967 }
4968 self.errors.push(self.engine_error(
4969 format!(
4970 "Reference target '{}' not found in spec '{}'",
4971 reference.name, containing_spec.name
4972 ),
4973 reference_source,
4974 ));
4975 return None;
4976 }
4977
4978 let (resolved_segments, target_spec) = self.resolve_path_segments(
4979 &reference.segments,
4980 reference_source,
4981 Vec::new(),
4982 containing_spec,
4983 containing_repository,
4984 effective,
4985 )?;
4986
4987 let is_data = target_spec
4988 .data
4989 .iter()
4990 .any(|d| d.reference.is_local() && d.reference.name == reference.name);
4991 let is_rule = target_spec.rules.iter().any(|r| r.name == reference.name);
4992
4993 if is_data && is_rule {
4994 self.errors.push(self.engine_error(
4995 format!(
4996 "Reference target '{}' is ambiguous: both a data and a rule in spec '{}'",
4997 reference.name, target_spec.name
4998 ),
4999 reference_source,
5000 ));
5001 return None;
5002 }
5003 if is_data {
5004 if target_spec.data.iter().any(|field| {
5005 field.reference.name == reference.name
5006 && field.reference.segments.is_empty()
5007 && matches!(field.value, ParsedDataValue::Import { .. })
5008 }) {
5009 self.errors.push(self.engine_error(
5010 format!(
5011 "cannot supply spec instance '{}' as a value",
5012 reference.name
5013 ),
5014 reference_source,
5015 ));
5016 return None;
5017 }
5018 return Some(ReferenceTarget::Data(DataPath {
5019 segments: resolved_segments,
5020 data: reference.name.clone(),
5021 }));
5022 }
5023 if is_rule {
5024 return Some(ReferenceTarget::Rule(RulePath {
5025 segments: resolved_segments,
5026 rule: reference.name.clone(),
5027 }));
5028 }
5029
5030 self.errors.push(self.engine_error(
5031 format!(
5032 "Reference target '{}' not found in spec '{}'",
5033 reference.name, target_spec.name
5034 ),
5035 reference_source,
5036 ));
5037 None
5038 }
5039
5040 fn build_data_bindings(
5043 &mut self,
5044 spec: &'a LemmaSpec,
5045 spec_repository: &Arc<LemmaRepository>,
5046 effective: &EffectiveDate,
5047 ) -> Result<DataBindings, Vec<Error>> {
5048 let mut bindings: DataBindings = IndexMap::new();
5049 let mut errors: Vec<Error> = Vec::new();
5050
5051 for import_row in &spec.data {
5052 if !import_row.reference.is_local() {
5053 continue;
5054 }
5055 let ParsedDataValue::Import {
5056 bindings: uses_bindings,
5057 ..
5058 } = &import_row.value
5059 else {
5060 continue;
5061 };
5062 for binding in uses_bindings {
5063 let binding_reference =
5064 ast::prefix_reference(&import_row.reference.name, &binding.path);
5065 let mut walk_spec = spec;
5066 let mut walk_repository = Arc::clone(spec_repository);
5067 let mut walk_effective = effective.clone();
5068 let mut binding_resolved = true;
5069
5070 for segment in &binding_reference.segments {
5071 let Some(seg_data) = walk_spec.data.iter().find(|field| {
5072 field.reference.segments.is_empty() && field.reference.name == *segment
5073 }) else {
5074 self.push_missing_with_data_field_error(
5075 import_row, binding, walk_spec, segment,
5076 );
5077 binding_resolved = false;
5078 break;
5079 };
5080
5081 match &seg_data.value {
5082 ParsedDataValue::Import {
5083 spec_ref,
5084 effective_ref: Some(_),
5085 ..
5086 } => {
5087 let targets = match self.dynamic_import_targets(
5088 spec_ref,
5089 walk_spec,
5090 &walk_repository,
5091 &binding.source_location,
5092 ) {
5093 Ok(targets) => targets,
5094 Err(error) => {
5095 self.errors.push(error);
5096 binding_resolved = false;
5097 break;
5098 }
5099 };
5100 let last_hop = binding_reference.segments.last() == Some(segment);
5101 if last_hop {
5102 let field = binding_reference.name.as_str();
5103 let mut missing_on = None;
5104 for target in &targets {
5105 if !target.spec.data.iter().any(|row| {
5106 row.reference.segments.is_empty()
5107 && row.reference.name == field
5108 }) {
5109 missing_on = Some(target.spec);
5110 break;
5111 }
5112 }
5113 if let Some(candidate) = missing_on {
5114 self.push_missing_with_data_field_error(
5115 import_row, binding, candidate, field,
5116 );
5117 binding_resolved = false;
5118 break;
5119 }
5120 }
5121 let first = targets
5122 .into_iter()
5123 .next()
5124 .expect("BUG: dynamic_import_targets returns at least one target");
5125 walk_effective = first.at;
5126 walk_repository = first.repository;
5127 walk_spec = first.spec;
5128 }
5129 ParsedDataValue::Import { spec_ref, .. } => {
5130 walk_spec = match self.resolve_spec_ref(
5131 spec_ref,
5132 &walk_effective,
5133 walk_spec,
5134 &walk_repository,
5135 ) {
5136 Ok((repository, arc)) => {
5137 walk_effective = spec_ref.at(&walk_effective);
5138 walk_repository = repository;
5139 arc
5140 }
5141 Err(error) => {
5142 self.errors.push(error);
5143 binding_resolved = false;
5144 break;
5145 }
5146 };
5147 }
5148 ParsedDataValue::Definition { .. }
5149 if self.is_data_instance_list(
5150 seg_data,
5151 walk_spec,
5152 &walk_repository,
5153 &walk_effective,
5154 ) =>
5155 {
5156 self.errors.push(Error::validation_with_context(
5157 format!(
5158 "binding `{path}` replaces instance list `{segment}`; supply the whole list through evaluation data",
5159 path = binding_reference,
5160 ),
5161 Some(binding.source_location.clone()),
5162 None::<String>,
5163 Some(self.main_spec),
5164 None,
5165 ));
5166 binding_resolved = false;
5167 break;
5168 }
5169 _ => {
5170 self.errors.push(Error::validation_with_context(
5171 format!(
5172 "`{segment}` in `{spec}` is not an imported spec — `-> with` paths step through data fields that hold a `uses` import.",
5173 spec = walk_spec.name
5174 ),
5175 Some(binding.source_location.clone()),
5176 Some(format!(
5177 "`-> with` on import `{alias}` must step through import data fields",
5178 alias = import_row.reference.name,
5179 )),
5180 Some(self.main_spec),
5181 None,
5182 ));
5183 binding_resolved = false;
5184 break;
5185 }
5186 }
5187 }
5188
5189 if !binding_resolved {
5190 continue;
5191 }
5192
5193 if !walk_spec.data.iter().any(|field| {
5194 field.reference.segments.is_empty()
5195 && field.reference.name == binding_reference.name
5196 }) {
5197 self.push_missing_with_data_field_error(
5198 import_row,
5199 binding,
5200 walk_spec,
5201 &binding_reference.name,
5202 );
5203 continue;
5204 }
5205
5206 let target_is_instance_list = walk_spec.data.iter().any(|field| {
5207 field.reference.segments.is_empty()
5208 && field.reference.name == binding_reference.name
5209 && self.is_data_instance_list(
5210 field,
5211 walk_spec,
5212 &walk_repository,
5213 &walk_effective,
5214 )
5215 });
5216
5217 let mut binding_key: Vec<String> = binding_reference.segments.clone();
5218 binding_key.push(binding_reference.name.clone());
5219
5220 let binding_value = match &binding.rhs {
5221 ast::UsesBindingRhs::Literal(value) => {
5222 if target_is_instance_list {
5223 self.errors.push(Error::validation_with_context(
5224 format!(
5225 "binding `{path}` replaces instance list `{name}` with a literal; list slots take `uses` aliases",
5226 path = binding_reference,
5227 name = binding_reference.name,
5228 ),
5229 Some(binding.source_location.clone()),
5230 None::<String>,
5231 Some(self.main_spec),
5232 None,
5233 ));
5234 continue;
5235 }
5236 BindingValue::Literal(value.clone())
5237 }
5238 ast::UsesBindingRhs::References { targets } if target_is_instance_list => {
5239 BindingValue::InstanceAliases(targets.clone())
5240 }
5241 ast::UsesBindingRhs::References { targets } => {
5242 let [target] = targets.as_slice() else {
5243 self.errors.push(Error::validation_with_context(
5244 format!(
5245 "binding `{binding_reference}` targets a scalar slot but supplies {} references",
5246 targets.len()
5247 ),
5248 Some(binding.source_location.clone()),
5249 None::<String>,
5250 Some(self.main_spec),
5251 None,
5252 ));
5253 continue;
5254 };
5255 let Some(resolved_target) = self.resolve_reference_target_in_spec(
5256 target,
5257 &binding.source_location,
5258 spec,
5259 spec_repository,
5260 effective,
5261 ) else {
5262 continue;
5263 };
5264 BindingValue::Reference {
5265 target: resolved_target,
5266 }
5267 }
5268 };
5269
5270 let source = binding.source_location.clone();
5271 if let Some((_, existing_source)) = bindings.get(&binding_key) {
5272 errors.push(self.engine_error(
5273 format!(
5274 "Duplicate data binding for '{}' (previously bound at {}:{})",
5275 binding_key.join("."),
5276 existing_source.source_type,
5277 existing_source.span.line
5278 ),
5279 &binding.source_location,
5280 ));
5281 } else {
5282 bindings.insert(binding_key, (binding_value, source));
5283 }
5284 }
5285 }
5286
5287 if !errors.is_empty() {
5288 return Err(errors);
5289 }
5290
5291 Ok(bindings)
5292 }
5293
5294 fn is_data_instance_list(
5296 &self,
5297 data: &LemmaData,
5298 current_spec: &LemmaSpec,
5299 current_repository: &Arc<LemmaRepository>,
5300 effective: &EffectiveDate,
5301 ) -> bool {
5302 let Some(spec_ref) = discovery::data_instance_list_spec_ref(current_spec, data) else {
5303 return false;
5304 };
5305 discovery::resolve_spec_ref_or_self(
5306 self.context,
5307 &spec_ref,
5308 current_repository,
5309 current_spec,
5310 effective,
5311 None,
5312 )
5313 .is_ok()
5314 }
5315
5316 fn is_duplicate_data(
5318 &mut self,
5319 data: &LemmaData,
5320 data_path: &DataPath,
5321 current_spec: &LemmaSpec,
5322 ) -> bool {
5323 let Some(existing) = self.data.get(data_path) else {
5324 return false;
5325 };
5326 let (message, suggestion) = if is_uses_vs_data_clash(existing, &data.value) {
5327 uses_vs_data_duplicate_message(current_spec, data, existing)
5328 } else {
5329 (
5330 format!(
5331 "The name '{}' is already used for data in this spec.",
5332 data_path.data
5333 ),
5334 None,
5335 )
5336 };
5337 self.errors.push(Error::validation_with_context(
5338 message,
5339 Some(data.source_location.clone()),
5340 suggestion,
5341 Some(self.main_spec),
5342 None,
5343 ));
5344 true
5345 }
5346
5347 fn add_data(
5350 &mut self,
5351 data: &LemmaData,
5352 current_spec: &'a LemmaSpec,
5353 current_repository: &Arc<LemmaRepository>,
5354 effective: &EffectiveDate,
5355 ) {
5356 let data_path = DataPath::local(data.reference.name.clone());
5357 if self.is_duplicate_data(data, &data_path, current_spec) {
5358 return;
5359 }
5360
5361 if let ParsedDataValue::Definition {
5362 base: Some(ParentType::List { .. }),
5363 list,
5364 ..
5365 } = &data.value
5366 {
5367 let is_instance =
5368 self.is_data_instance_list(data, current_spec, current_repository, effective);
5369 if list.is_some() && !is_instance {
5370 self.errors.push(Error::validation_with_context(
5371 "value lists are numbered; `-> key` is for instance lists".to_string(),
5372 Some(data.source_location.clone()),
5373 None::<String>,
5374 Some(self.main_spec),
5375 None,
5376 ));
5377 return;
5378 }
5379 }
5380
5381 let (original_schema_type, original_declared_suggestion, original_declared_value) = if matches!(
5382 &data.value,
5383 ParsedDataValue::Definition { .. }
5384 )
5385 && data.value.definition_needs_type_resolution()
5386 && !self.is_data_instance_list(data, current_spec, current_repository, effective)
5387 {
5388 let resolved = self
5389 .local_types
5390 .iter()
5391 .find(|(_, s, _)| discovery::same_loaded_spec(s, current_spec))
5392 .map(|(_, _, t)| t)
5393 .expect("BUG: no resolved types for spec during add_local_data");
5394 let lemma_type = Arc::clone(
5395 resolved
5396 .resolved
5397 .get(&data.reference.name)
5398 .expect(
5399 "BUG: type not in ResolvedSpecTypes.resolved. TypeResolver should have registered it",
5400 ),
5401 );
5402 let declared = resolved
5403 .declared_suggestions
5404 .get(&data.reference.name)
5405 .cloned();
5406 let declared_value = resolved.declared_values.get(&data.reference.name).cloned();
5407 (Some(lemma_type), declared, declared_value)
5408 } else {
5409 (None, None, None)
5410 };
5411
5412 let effective_source = data.source_location.clone();
5413
5414 match &data.value {
5415 ParsedDataValue::Definition { .. }
5416 if self.is_data_instance_list(
5417 data,
5418 current_spec,
5419 current_repository,
5420 effective,
5421 ) =>
5422 {
5423 unreachable!("BUG: instance list '{data_path}' is opened by its spec frame")
5424 }
5425 ParsedDataValue::Definition { .. } if data.value.is_definition_literal_only() => {
5426 let ParsedDataValue::Definition {
5427 value: Some(value), ..
5428 } = &data.value
5429 else {
5430 unreachable!("BUG: literal-only Definition must carry value");
5431 };
5432 self.insert_literal_data(
5433 data_path,
5434 value,
5435 original_schema_type,
5436 effective_source,
5437 current_spec,
5438 DataOrigin::Declared,
5439 );
5440 }
5441 ParsedDataValue::Definition { .. } => {
5442 let resolved_type = original_schema_type.unwrap_or_else(|| {
5443 unreachable!(
5444 "BUG: Definition without schema — TypeResolver should have registered it"
5445 )
5446 });
5447 let declared_suggestion = original_declared_suggestion;
5448 let declared_value = original_declared_value;
5449
5450 let is_generic_measure_range = matches!(
5451 &resolved_type.specifications,
5452 TypeSpecification::MeasureRange {
5453 units,
5454 decomposition,
5455 ..
5456 } if units.0.is_empty() && decomposition.is_none()
5457 );
5458
5459 if is_generic_measure_range {
5460 let measure_endpoint_suggest = matches!(
5461 &declared_suggestion,
5462 Some(BoundValueKind {
5463 value: ValueKind::Range(left, right),
5464 ..
5465 }) if matches!(
5466 (&left.value, &right.value),
5467 (ValueKind::Measure(_), ValueKind::Measure(_))
5468 )
5469 );
5470 let measure_endpoint_fill = matches!(
5471 &declared_value,
5472 Some(BoundValueKind {
5473 value: ValueKind::Range(left, right),
5474 ..
5475 }) if matches!(
5476 (&left.value, &right.value),
5477 (ValueKind::Measure(_), ValueKind::Measure(_))
5478 )
5479 );
5480 if measure_endpoint_suggest || measure_endpoint_fill {
5481 self.errors.push(self.engine_error(
5482 "measure range requires unit declarations before a measure-endpoint suggest or with".to_string(),
5483 &effective_source,
5484 ));
5485 return;
5486 }
5487 }
5488
5489 self.data.insert(
5490 data_path,
5491 DataDefinition::TypeDeclaration {
5492 resolved_type,
5493 declared_suggestion,
5494 declared_value,
5495 source: effective_source,
5496 },
5497 );
5498 }
5499 ParsedDataValue::ImportList { .. } => {
5500 panic!("BUG: uses list is rejected at parse and must not reach planning");
5501 }
5502 ParsedDataValue::Import { .. } => {
5503 unreachable!("BUG: `uses {data_path}` is added as an import row")
5504 }
5505 }
5506 }
5507
5508 fn plan_instance_list(
5512 &mut self,
5513 data: &'a LemmaData,
5514 current_spec: &'a LemmaSpec,
5515 current_repository: &Arc<LemmaRepository>,
5516 effective: &EffectiveDate,
5517 bindings: &DataBindings,
5518 ) {
5519 let data_path = DataPath::local(data.reference.name.clone());
5520 if self.is_duplicate_data(data, &data_path, current_spec) {
5521 return;
5522 }
5523 let Some(spec_ref) = discovery::data_instance_list_spec_ref(current_spec, data) else {
5524 panic!("BUG: plan_instance_list on non-instance-list data '{data_path}'");
5525 };
5526 let list = match &data.value {
5527 ParsedDataValue::Definition {
5528 list: Some(list), ..
5529 } => list,
5530 ParsedDataValue::Definition { list: None, .. } => &EMPTY_INSTANCE_LIST,
5531 _ => panic!("BUG: plan_instance_list requires Definition on '{data_path}'"),
5532 };
5533 let source = data.source_location.clone();
5534 let (row_repository, row_spec) = match discovery::resolve_spec_ref_or_self(
5535 self.context,
5536 &spec_ref,
5537 current_repository,
5538 current_spec,
5539 effective,
5540 None,
5541 ) {
5542 Ok(pair) => pair,
5543 Err(e) => {
5544 self.errors.push(e);
5545 return;
5546 }
5547 };
5548 let at = spec_ref.at(effective);
5549 let Some(identity) = self.identity_at(&row_repository, row_spec, &at) else {
5550 return;
5551 };
5552
5553 if discovery::same_loaded_spec(row_spec, current_spec) && !list.rows.is_empty() {
5554 self.errors.push(self.engine_error(
5555 format!(
5556 "`{}` is a list of `{}` inside `{}` itself: it cannot have source rows, \
5557 since every row would hold the same rows forever; rows come from run data",
5558 data_path.data, identity.spec, identity.spec
5559 ),
5560 &source,
5561 ));
5562 return;
5563 }
5564
5565 let template = if identity == self.instance_lists.self_identity {
5566 assert!(
5567 discovery::same_loaded_spec(row_spec, self.main_spec),
5568 "BUG: self-list row identity equals the planned spec's identity but is another spec"
5569 );
5570 None
5571 } else {
5572 match self.row_template(&identity, &row_repository, row_spec, &at) {
5573 Some(template) => Some(template),
5574 None => return,
5575 }
5576 };
5577 let key = match &template {
5578 Some(template) => self.instance_list_key(list, &template.data, &identity),
5579 None => self.instance_list_key(list, &self.data, &identity),
5580 };
5581 let key = match key {
5582 Ok(key) => key,
5583 Err(errors) => {
5584 self.errors.extend(errors);
5585 return;
5586 }
5587 };
5588
5589 let store = match &template {
5590 Some(template) => {
5591 let row_identity = Arc::new(identity.clone());
5592 let rows = self.written_rows(
5593 &ListSite {
5594 list_path: &data_path,
5595 identity: &row_identity,
5596 template,
5597 spec: current_spec,
5598 repository: current_repository,
5599 effective,
5600 bindings,
5601 },
5602 list,
5603 );
5604 self.row_store(rows, &key, &source, template)
5605 }
5606 None => Arc::new(RowStore::unkeyed(Vec::new())),
5607 };
5608
5609 let element = Arc::new(LemmaType::primitive(TypeSpecification::Spec {
5610 spec: identity.clone(),
5611 }));
5612 let mut resolved_type = LemmaType {
5613 name: Some(format!("{} list", identity.spec)),
5614 specifications: TypeSpecification::List {
5615 element,
5616 key: key.clone(),
5617 help: String::new(),
5618 },
5619 extends: TypeExtends::Primitive,
5620 options: TypeOptions::default(),
5621 };
5622 if let ParsedDataValue::Definition {
5623 constraints: Some(constraints),
5624 ..
5625 } = &data.value
5626 {
5627 let mut declared_suggestion = None;
5628 let mut declared_value = None;
5629 match apply_constraints_to_spec(
5630 current_spec,
5631 &data_path.data,
5632 &data_path.data,
5633 &mut resolved_type,
5634 constraints,
5635 &source,
5636 ConstraintSuggestionSink {
5637 suggestion: &mut declared_suggestion,
5638 value: &mut declared_value,
5639 resolve_unit: None,
5640 },
5641 ) {
5642 Ok(()) => {
5643 if let Some(raw) = declared_value {
5644 self.errors.extend(instance_list_option_errors(
5645 &resolved_type,
5646 &raw,
5647 &source,
5648 current_spec,
5649 ));
5650 }
5651 }
5652 Err(errors) => self.errors.extend(errors),
5653 }
5654 }
5655 self.reject_spec_option_rows(list);
5656 let resolved_type = Arc::new(resolved_type);
5657 self.data.insert(
5658 data_path.clone(),
5659 DataDefinition::InstanceList {
5660 resolved_type,
5661 origin: DataOrigin::Declared,
5662 source,
5663 },
5664 );
5665 let source = InstanceListSource::new(identity, key, store);
5666 self.instance_lists.sources.insert(data_path, source);
5667 }
5668
5669 fn reject_spec_option_rows(&mut self, list: &ast::InstanceListBody) {
5670 for row in &list.option_rows {
5671 let ast::InstanceListRow::Fields {
5672 source_location, ..
5673 } = row
5674 else {
5675 unreachable!("BUG: -> option rows are field rows");
5676 };
5677 self.errors
5678 .push(self.engine_error(SPEC_LIST_HAS_NO_OPTIONS.to_string(), source_location));
5679 }
5680 }
5681
5682 fn row_template_of(&self, identity: &SpecIdentity) -> Option<RowTemplate> {
5687 self.instance_lists.row_template_of(identity)
5688 }
5689
5690 fn missing_row_template_error(&self, identity: &SpecIdentity, source: &Source) -> Error {
5691 self.engine_error(
5692 format!(
5693 "instance list of `{}` needs `uses {}` before `-> key` or `-> with`",
5694 identity.spec, identity.spec
5695 ),
5696 source,
5697 )
5698 }
5699
5700 fn conversion_root_rules(&self) -> BTreeSet<RulePath> {
5703 let mut rules = BTreeSet::new();
5704 for rule in &self.main_spec.rules {
5705 rules.insert(RulePath {
5706 segments: Vec::new(),
5707 rule: rule.name.clone(),
5708 });
5709 }
5710 for path in self.rules.keys() {
5711 rules.insert(path.clone());
5712 }
5713 rules
5714 }
5715
5716 fn template_row_chain(
5718 &self,
5719 identity: &SpecIdentity,
5720 template: &RowTemplate,
5721 parts: &[&str],
5722 ) -> Option<Vec<RowHop>> {
5723 match template {
5724 RowTemplate::Plan(plan) => instance_list::row_end_chain(
5725 identity,
5726 instance_list::RowEndView::Plan(plan),
5727 &self.instance_lists.templates,
5728 parts,
5729 ),
5730 RowTemplate::Root => {
5731 let rules = self.conversion_root_rules();
5732 instance_list::row_end_chain(
5733 identity,
5734 instance_list::RowEndView::Root {
5735 data: &self.data,
5736 rules: &rules,
5737 },
5738 &self.instance_lists.templates,
5739 parts,
5740 )
5741 }
5742 }
5743 }
5744
5745 fn identity_at(
5747 &mut self,
5748 repository: &Arc<LemmaRepository>,
5749 spec: &LemmaSpec,
5750 at: &EffectiveDate,
5751 ) -> Option<SpecIdentity> {
5752 match self
5753 .interners
5754 .slices
5755 .identity_at(self.context, repository, spec, at, self.limits)
5756 {
5757 Ok(identity) => Some(identity),
5758 Err(errors) => {
5759 self.nested_errors.extend(errors);
5760 self.nested_spec_failed = true;
5761 None
5762 }
5763 }
5764 }
5765
5766 fn row_template(
5771 &mut self,
5772 identity: &SpecIdentity,
5773 row_repository: &Arc<LemmaRepository>,
5774 row_spec: &'a LemmaSpec,
5775 at: &EffectiveDate,
5776 ) -> Option<Arc<ExecutionPlan>> {
5777 if let Some(template) = self.instance_lists.templates.get(identity) {
5778 return Some(Arc::clone(template));
5779 }
5780 if let Some(template) = self.interners.spec_templates.get(identity) {
5781 self.record_template(identity.clone(), Arc::clone(&template));
5782 return Some(template);
5783 }
5784 match super::get_or_build_template(
5785 self.context,
5786 row_repository,
5787 row_spec,
5788 at,
5789 self.limits,
5790 self.interners,
5791 ) {
5792 Ok(template) => {
5793 assert_eq!(
5794 template.identity, *identity,
5795 "BUG: the template built at an instant is the plan of the slice that holds it"
5796 );
5797 self.record_template(identity.clone(), Arc::clone(&template));
5798 Some(template)
5799 }
5800 Err(errors) => {
5801 self.nested_errors.extend(errors);
5802 self.nested_spec_failed = true;
5803 None
5804 }
5805 }
5806 }
5807
5808 fn record_template(&mut self, identity: SpecIdentity, template: Arc<ExecutionPlan>) {
5810 for (row_identity, row_plan) in &template.row_templates {
5811 self.instance_lists
5812 .templates
5813 .entry(row_identity.clone())
5814 .or_insert_with(|| Arc::clone(row_plan));
5815 }
5816 self.instance_lists
5817 .templates
5818 .entry(identity)
5819 .or_insert(template);
5820 }
5821
5822 fn instance_list_key(
5825 &self,
5826 list: &ast::InstanceListBody,
5827 row_data: &IndexMap<DataPath, DataDefinition>,
5828 identity: &SpecIdentity,
5829 ) -> Result<ListKey, Vec<Error>> {
5830 let Some(declared) = &list.key else {
5831 return Ok(ListKey::Numbered);
5832 };
5833 let mut key_fields: Vec<KeyField> = Vec::with_capacity(declared.fields.len());
5834 let mut errors = Vec::new();
5835 for field in &declared.fields {
5836 if !field.segments.is_empty() {
5837 errors.push(self.engine_error(
5838 format!(
5839 "key field `{field}` is a dotted path; a key names data declared on `{}` itself",
5840 identity.spec
5841 ),
5842 &declared.source_location,
5843 ));
5844 continue;
5845 }
5846 if key_fields
5847 .iter()
5848 .any(|key_field| key_field.name == field.name)
5849 {
5850 errors.push(self.engine_error(
5851 format!("`{}` appears twice in `-> key`", field.name),
5852 &declared.source_location,
5853 ));
5854 continue;
5855 }
5856 let local = DataPath::local(field.name.clone());
5857 let Some(definition) = row_data.get(&local) else {
5858 errors.push(self.engine_error(
5859 format!(
5860 "key field `{}` is not data on `{}`",
5861 field.name, identity.spec
5862 ),
5863 &declared.source_location,
5864 ));
5865 continue;
5866 };
5867 match definition {
5868 DataDefinition::Value { .. }
5869 | DataDefinition::TypeDeclaration { .. }
5870 | DataDefinition::Reference { .. } => {}
5871 DataDefinition::Import { .. } | DataDefinition::InstanceList { .. } => {
5872 errors.push(self.engine_error(
5873 format!(
5874 "key field `{}` on `{}` is not a value",
5875 field.name, identity.spec
5876 ),
5877 &declared.source_location,
5878 ));
5879 continue;
5880 }
5881 }
5882 key_fields.push(KeyField {
5883 name: field.name.clone(),
5884 path: local,
5885 });
5886 }
5887 if errors.is_empty() {
5888 Ok(ListKey::Fields(key_fields))
5889 } else {
5890 Err(errors)
5891 }
5892 }
5893 fn written_rows<'r>(
5896 &mut self,
5897 site: &ListSite<'_, 'a>,
5898 list: &'r ast::InstanceListBody,
5899 ) -> Vec<Arc<RowInstance>> {
5900 let mut written_fields: HashMap<&'r ast::Reference, WrittenField> = HashMap::new();
5901 let list_name: Arc<str> = Arc::from(site.list_path.data.as_str());
5902 let spec_name: Arc<str> = Arc::from(site.identity.spec.as_str());
5903 let repository: Option<Arc<str>> = site.identity.repository.as_deref().map(Arc::from);
5904 let row_prefix = |number: usize| {
5905 vec![PathSegment::list_row_shared(
5906 &list_name,
5907 number,
5908 &spec_name,
5909 repository.as_ref(),
5910 )]
5911 };
5912 let mut rows = Vec::new();
5913 let mut number = 0;
5914 for row in &list.rows {
5915 match row {
5916 ast::InstanceListRow::Fields {
5917 bindings: fields,
5918 source_location,
5919 } => {
5920 number += 1;
5921 if let Some(row) = self.field_stored_row(
5922 site,
5923 fields,
5924 &mut written_fields,
5925 source_location,
5926 row_prefix(number),
5927 ) {
5928 rows.push(row);
5929 }
5930 }
5931 ast::InstanceListRow::Values {
5932 values,
5933 source_location,
5934 } => {
5935 for value in values {
5936 number += 1;
5937 if let Some(row) =
5938 self.named_stored_row(site, value, source_location, row_prefix(number))
5939 {
5940 rows.push(row);
5941 }
5942 }
5943 }
5944 }
5945 }
5946 rows
5947 }
5948
5949 fn resolve_written_field(
5951 &self,
5952 site: &ListSite<'_, 'a>,
5953 path: &ast::Reference,
5954 ) -> WrittenField {
5955 let parts: Vec<&str> = path
5956 .segments
5957 .iter()
5958 .map(String::as_str)
5959 .chain(std::iter::once(path.name.as_str()))
5960 .collect();
5961 let Some(chain) = instance_list::row_end_chain(
5962 site.identity,
5963 instance_list::RowEndView::Plan(site.template),
5964 &self.instance_lists.templates,
5965 &parts,
5966 ) else {
5967 return WrittenField::NotData;
5968 };
5969 let last = chain.last().expect("BUG: a row end chain has a last hop");
5970 let RowEnd::Data(data_path) = &last.end else {
5971 return WrittenField::NotData;
5972 };
5973 let template = if chain.len() == 1 {
5974 Arc::clone(site.template)
5975 } else {
5976 self.template_of(&last.identity)
5977 };
5978 match template
5979 .data
5980 .get(data_path)
5981 .expect("BUG: row end chain data is template data")
5982 {
5983 DataDefinition::Import { row, .. } => WrittenField::Import {
5984 identity: Arc::clone(&row.identity),
5985 },
5986 DataDefinition::InstanceList { .. } => WrittenField::List,
5987 DataDefinition::Value { .. }
5988 | DataDefinition::TypeDeclaration { .. }
5989 | DataDefinition::Reference { .. } => WrittenField::Value {
5990 key: parts.iter().map(|part| (*part).to_string()).collect(),
5991 },
5992 }
5993 }
5994
5995 fn field_stored_row<'r>(
5999 &mut self,
6000 site: &ListSite<'_, 'a>,
6001 fields: &'r [ast::RowBinding],
6002 written_fields: &mut HashMap<&'r ast::Reference, WrittenField>,
6003 source: &Source,
6004 prefix: Vec<PathSegment>,
6005 ) -> Option<Arc<RowInstance>> {
6006 let mut bindings: DataBindings = IndexMap::new();
6007 let mut failed = false;
6008 for binding in fields {
6009 let field = match written_fields.get(&binding.path) {
6010 Some(field) => field,
6011 None => {
6012 let resolved = self.resolve_written_field(site, &binding.path);
6013 written_fields.entry(&binding.path).or_insert(resolved)
6014 }
6015 };
6016 let key = match field {
6017 WrittenField::Value { key } => key,
6018 WrittenField::NotData => {
6019 self.errors.push(self.engine_error(
6020 format!(
6021 "row field `{}` is not data on `{}`",
6022 binding.path, site.identity.spec
6023 ),
6024 &binding.source_location,
6025 ));
6026 failed = true;
6027 continue;
6028 }
6029 WrittenField::Import { identity } => {
6030 let name = &binding.path;
6031 self.errors.push(self.engine_error(
6032 format!(
6033 "row field `{name}` is a `uses` instance of `{}`; bind its fields with `{name}.<field>`",
6034 identity.spec
6035 ),
6036 &binding.source_location,
6037 ));
6038 failed = true;
6039 continue;
6040 }
6041 WrittenField::List => {
6042 self.errors.push(self.engine_error(
6043 format!(
6044 "row field `{}` is a list on `{}`; a row binds values, not lists",
6045 binding.path, site.identity.spec
6046 ),
6047 &binding.source_location,
6048 ));
6049 failed = true;
6050 continue;
6051 }
6052 };
6053 if bindings.contains_key(key) {
6054 self.errors.push(self.engine_error(
6055 format!("row field `{}` appears twice in one row", binding.path),
6056 &binding.source_location,
6057 ));
6058 failed = true;
6059 continue;
6060 }
6061 let key = key.clone();
6062 let value = match &binding.rhs {
6063 WithRhs::Literal(value) => BindingValue::Literal(value.clone()),
6064 WithRhs::Reference { target } => match self.resolve_reference_target_in_spec(
6065 target,
6066 &binding.source_location,
6067 site.spec,
6068 site.repository,
6069 site.effective,
6070 ) {
6071 Some(target) => BindingValue::Reference { target },
6072 None => {
6073 failed = true;
6074 continue;
6075 }
6076 },
6077 };
6078 bindings.insert(key, (value, binding.source_location.clone()));
6079 }
6080 if failed {
6081 return None;
6082 }
6083 Some(self.binding_row(RowWrite {
6084 identity: Arc::clone(site.identity),
6085 template: Arc::clone(site.template),
6086 origin: RowOrigin::Written {
6087 instance: SpecInstance {
6088 prefix: prefix.clone(),
6089 },
6090 source: source.clone(),
6091 },
6092 base: vec![None; site.template.data.len()],
6093 bindings,
6094 prefix,
6095 hops: 0,
6096 }))
6097 }
6098
6099 fn named_stored_row(
6101 &mut self,
6102 site: &ListSite<'_, 'a>,
6103 value: &WithRhs,
6104 source: &Source,
6105 prefix: Vec<PathSegment>,
6106 ) -> Option<Arc<RowInstance>> {
6107 let list = &site.list_path.data;
6108 let target = match value {
6109 WithRhs::Reference { target } if target.segments.is_empty() => target,
6110 WithRhs::Reference { target } => {
6111 self.errors.push(self.engine_error(
6112 format!(
6113 "row `{target}` of `{list}` must name a `uses` instance of `{}` declared in this spec",
6114 site.identity.spec
6115 ),
6116 source,
6117 ));
6118 return None;
6119 }
6120 WithRhs::Literal(literal) => {
6121 self.errors.push(self.engine_error(
6122 format!(
6123 "row `{literal}` of `{list}` is not a `{}` instance; write `-> with <field>: <value>` for an inline row",
6124 site.identity.spec
6125 ),
6126 source,
6127 ));
6128 return None;
6129 }
6130 };
6131 let instance_identity = match self.data.get(&DataPath::local(target.name.clone())) {
6132 Some(DataDefinition::Import { row, .. }) => Arc::clone(&row.identity),
6133 Some(_) | None => {
6134 self.errors.push(self.engine_error(
6135 format!(
6136 "row `{target}` of `{list}` is not a `uses` instance of `{}`",
6137 site.identity.spec
6138 ),
6139 source,
6140 ));
6141 return None;
6142 }
6143 };
6144 if instance_identity != *site.identity {
6145 self.errors.push(self.engine_error(
6146 format!(
6147 "`{}` is a `{}` instance; rows of `{list}` are `{}`",
6148 target.name, instance_identity, site.identity
6149 ),
6150 source,
6151 ));
6152 return None;
6153 }
6154 Some(self.binding_row(RowWrite {
6155 identity: instance_identity,
6156 template: Arc::clone(site.template),
6157 origin: RowOrigin::Written {
6158 instance: SpecInstance {
6159 prefix: prefix.clone(),
6160 },
6161 source: source.clone(),
6162 },
6163 base: vec![None; site.template.data.len()],
6164 bindings: bindings_under(site.bindings, &target.name),
6165 prefix,
6166 hops: 0,
6167 }))
6168 }
6169
6170 fn nested_row_template(
6172 &self,
6173 template: &ExecutionPlan,
6174 path: &DataPath,
6175 ) -> (Arc<SpecIdentity>, Arc<ExecutionPlan>) {
6176 let source = template
6177 .instance_lists
6178 .get(path)
6179 .unwrap_or_else(|| panic!("BUG: list binding '{path}' is not a list on the row spec"));
6180 let plan = match self.row_template_of(&source.identity) {
6181 Some(RowTemplate::Plan(plan)) => plan,
6182 Some(RowTemplate::Root) => {
6183 panic!(
6184 "BUG: copied rows of the root spec need an import cycle, rejected at discovery"
6185 )
6186 }
6187 None => panic!(
6188 "BUG: row template of '{}' was planned before its consumers",
6189 source.identity.spec
6190 ),
6191 };
6192 (Arc::new(source.identity.clone()), plan)
6193 }
6194
6195 fn row_store(
6198 &mut self,
6199 rows: Vec<Arc<RowInstance>>,
6200 key: &ListKey,
6201 source: &Source,
6202 template: &ExecutionPlan,
6203 ) -> Arc<RowStore> {
6204 let Some(store_key) = self.row_store_key(&rows, key, source, template) else {
6205 return Arc::new(RowStore::unkeyed(rows));
6206 };
6207 match RowStore::keyed(rows, store_key) {
6208 Ok(store) => Arc::new(store),
6209 Err((message, unkeyed)) => {
6210 self.errors.push(self.engine_error(message, source));
6211 Arc::new(unkeyed)
6212 }
6213 }
6214 }
6215
6216 fn row_store_key(
6220 &mut self,
6221 rows: &[Arc<RowInstance>],
6222 key: &ListKey,
6223 source: &Source,
6224 template: &ExecutionPlan,
6225 ) -> Option<Vec<StoreKeyField>> {
6226 let ListKey::Fields(key_fields) = key else {
6227 return None;
6228 };
6229 let store_key: Vec<StoreKeyField> = key_fields
6230 .iter()
6231 .map(|key_field| StoreKeyField {
6232 name: key_field.name.clone(),
6233 slot: template
6234 .data
6235 .get_index_of(&key_field.path)
6236 .expect("BUG: key field is template data"),
6237 field_type: instance_list::row_end_type(
6238 template,
6239 &RowEnd::Data(key_field.path.clone()),
6240 ),
6241 default: instance_list::default_of(&template.data, &key_field.path),
6242 })
6243 .collect();
6244 for (index, row) in rows.iter().enumerate() {
6245 for field in &store_key {
6246 let name = &field.name;
6247 match &row.bindings[field.slot] {
6248 Some(RowBinding::Bound(OperationResult::Value(_))) => {}
6249 Some(RowBinding::Bound(OperationResult::Veto(_))) => {
6250 unreachable!("BUG: stored row literal binding is never a Veto")
6251 }
6252 Some(RowBinding::Rows { .. } | RowBinding::Instance(_)) => {
6253 unreachable!("BUG: key fields are values, never lists or instances")
6254 }
6255 Some(RowBinding::Deferred { target, .. }) => {
6256 let target = match target {
6257 ReferenceTarget::Data(path) => path.input_key(),
6258 ReferenceTarget::Rule(path) => path.input_key(),
6259 };
6260 self.errors.push(self.engine_error(
6261 format!(
6262 "key field `{name}` of row {} is `{target}`; key values must be written as values",
6263 index + 1
6264 ),
6265 source,
6266 ));
6267 return None;
6268 }
6269 None if field.default.is_some() => {}
6270 None => {
6271 self.errors.push(self.engine_error(
6272 format!("row {} has no value for key field `{name}`", index + 1),
6273 source,
6274 ));
6275 return None;
6276 }
6277 }
6278 }
6279 }
6280 Some(store_key)
6281 }
6282
6283 fn insert_literal_data(
6287 &mut self,
6288 data_path: DataPath,
6289 value: &ast::Value,
6290 declared_schema_type: Option<Arc<LemmaType>>,
6291 effective_source: Source,
6292 current_spec: &LemmaSpec,
6293 origin: DataOrigin,
6294 ) {
6295 let Some((semantic_value, lemma_type)) =
6296 self.literal_to_typed(value, declared_schema_type, &effective_source, current_spec)
6297 else {
6298 return;
6299 };
6300 let literal_value = LiteralValue {
6301 value: semantic_value,
6302 };
6303 self.data.insert(
6304 data_path,
6305 DataDefinition::Value {
6306 value: literal_value,
6307 resolved_type: lemma_type,
6308 origin,
6309 source: effective_source,
6310 },
6311 );
6312 }
6313
6314 fn literal_to_typed(
6318 &mut self,
6319 value: &ast::Value,
6320 declared_schema_type: Option<Arc<LemmaType>>,
6321 effective_source: &Source,
6322 current_spec: &LemmaSpec,
6323 ) -> Option<(ValueKind, Arc<LemmaType>)> {
6324 let semantic_value = if let Some(ref schema) = declared_schema_type {
6325 match value {
6326 Value::Range(left, right)
6327 if matches!(
6328 &schema.specifications,
6329 TypeSpecification::MeasureRange { .. }
6330 | TypeSpecification::RatioRange { .. }
6331 ) =>
6332 {
6333 let element = schema.range_element_type();
6334 let resolve = unit_tokens_for_spec(&self.local_types, current_spec);
6335 let left_lit = match lift_range_endpoint(left, element.as_ref(), Some(&resolve))
6336 {
6337 Ok(lit) => lit,
6338 Err(e) => {
6339 self.errors.push(self.engine_error(e, effective_source));
6340 return None;
6341 }
6342 };
6343 let right_lit =
6344 match lift_range_endpoint(right, element.as_ref(), Some(&resolve)) {
6345 Ok(lit) => lit,
6346 Err(e) => {
6347 self.errors.push(self.engine_error(e, effective_source));
6348 return None;
6349 }
6350 };
6351 ValueKind::Range(Box::new(left_lit), Box::new(right_lit))
6352 }
6353 _ => {
6354 let resolve = unit_tokens_for_spec(&self.local_types, current_spec);
6355 match parser_value_to_value_kind(value, schema.as_ref(), Some(&resolve)) {
6356 Ok(s) => s,
6357 Err(e) => {
6358 self.errors.push(self.engine_error(e, effective_source));
6359 return None;
6360 }
6361 }
6362 }
6363 }
6364 } else {
6365 match value {
6366 Value::NumberWithUnit(magnitude, unit) => {
6367 let (bare, lt) = match self.resolve_unit_ref(current_spec, unit) {
6368 Ok(resolved) => resolved,
6369 Err(message) => {
6370 self.errors
6371 .push(self.engine_error(message, effective_source));
6372 return None;
6373 }
6374 };
6375 match number_with_unit_to_value_kind(*magnitude, &bare, lt.as_ref()) {
6376 Ok(s) => s,
6377 Err(e) => {
6378 self.errors.push(self.engine_error(e, effective_source));
6379 return None;
6380 }
6381 }
6382 }
6383 Value::Range(left, right) => match (left.as_ref(), right.as_ref()) {
6384 (
6385 Value::NumberWithUnit(left_mag, left_unit),
6386 Value::NumberWithUnit(right_mag, right_unit),
6387 ) => {
6388 match self.measure_ratio_range_from_number_with_unit_endpoints(
6389 current_spec,
6390 *left_mag,
6391 left_unit,
6392 *right_mag,
6393 right_unit,
6394 ) {
6395 Ok(s) => s,
6396 Err(e) => {
6397 self.errors.push(self.engine_error(e, effective_source));
6398 return None;
6399 }
6400 }
6401 }
6402 _ => match value_to_semantic(value) {
6403 Ok(s) => s,
6404 Err(e) => {
6405 self.errors.push(self.engine_error(e, effective_source));
6406 return None;
6407 }
6408 },
6409 },
6410 _ => match value_to_semantic(value) {
6411 Ok(s) => s,
6412 Err(e) => {
6413 self.errors.push(self.engine_error(e, effective_source));
6414 return None;
6415 }
6416 },
6417 }
6418 };
6419 let schema_type: Arc<LemmaType> = match declared_schema_type {
6420 Some(schema) => schema,
6421 None => match value {
6422 Value::Text(_) => primitive_text_arc().clone(),
6423 Value::Number(_) => primitive_number_arc().clone(),
6424 Value::NumberWithUnit(_, unit) => match self.resolve_unit_ref(current_spec, unit) {
6425 Ok((_, lt)) => lt,
6426 Err(message) => {
6427 self.errors
6428 .push(self.engine_error(message, effective_source));
6429 return None;
6430 }
6431 },
6432 Value::Boolean(_) => primitive_boolean_arc().clone(),
6433 Value::Date(_) => primitive_date_arc().clone(),
6434 Value::Time(_) => primitive_time_arc().clone(),
6435 Value::Range(left_v, right_v) => match (left_v.as_ref(), right_v.as_ref()) {
6436 (Value::NumberWithUnit(_, _), Value::NumberWithUnit(_, _)) => {
6437 match &semantic_value {
6438 ValueKind::Range(left, right) => {
6439 let specs = range_type_specification_from_endpoints(
6440 left.lemma_type.as_ref(),
6441 right.lemma_type.as_ref(),
6442 )
6443 .unwrap_or_else(|| {
6444 unreachable!(
6445 "BUG: measure/ratio range endpoints must form a range type after resolve"
6446 )
6447 });
6448 Arc::new(LemmaType::primitive(specs))
6449 }
6450 _ => unreachable!(
6451 "BUG: semantic range literal conversion returned non-range value kind"
6452 ),
6453 }
6454 }
6455 _ => match &semantic_value {
6456 ValueKind::Range(left, right) => {
6457 let endpoint_type = |endpoint: &TypedLiteral| -> Arc<LemmaType> {
6458 match &endpoint.value {
6459 ValueKind::Number(_) => primitive_number_arc().clone(),
6460 ValueKind::Date(_) => primitive_date_arc().clone(),
6461 ValueKind::Time(_) => primitive_time_arc().clone(),
6462 ValueKind::Ratio(_) => primitive_ratio_arc().clone(),
6463 ValueKind::Measure(_) => Arc::clone(&endpoint.lemma_type),
6464 other => unreachable!(
6465 "BUG: untyped range endpoint kind for insert_literal_data: {other:?}"
6466 ),
6467 }
6468 };
6469 let specs = range_type_specification_from_endpoints(
6470 endpoint_type(left).as_ref(),
6471 endpoint_type(right).as_ref(),
6472 )
6473 .unwrap_or_else(|| {
6474 unreachable!(
6475 "BUG: attempted to construct a range literal from incompatible endpoint types"
6476 )
6477 });
6478 Arc::new(LemmaType::primitive(specs))
6479 }
6480 _ => unreachable!(
6481 "BUG: semantic range literal conversion returned non-range value kind"
6482 ),
6483 },
6484 },
6485 },
6486 };
6487 let lemma_type = match (value, &semantic_value) {
6488 (Value::NumberWithUnit(_, unit), ValueKind::Measure(_) | ValueKind::Ratio(_)) => {
6489 let bare = match self.resolve_unit_ref(current_spec, unit) {
6490 Ok((bare, _)) => bare,
6491 Err(message) => {
6492 self.errors
6493 .push(self.engine_error(message, effective_source));
6494 return None;
6495 }
6496 };
6497 Arc::new(schema_type.as_ref().clone().with_unit(bare))
6498 }
6499 _ => schema_type,
6500 };
6501 Some((semantic_value, lemma_type))
6502 }
6503
6504 fn resolve_path_segments(
6506 &mut self,
6507 segments: &[String],
6508 reference_source: &Source,
6509 mut path_segments: Vec<PathSegment>,
6510 mut spec_context: &'a LemmaSpec,
6511 spec_context_repository: &Arc<LemmaRepository>,
6512 effective: &EffectiveDate,
6513 ) -> Option<(Vec<PathSegment>, &'a LemmaSpec)> {
6514 let mut last_arc: Option<&LemmaSpec> = None;
6515 let mut walk_effective = effective.clone();
6516 let mut context_repository = Arc::clone(spec_context_repository);
6517
6518 for segment in segments.iter() {
6519 let Some(data_ref) = spec_context
6520 .data
6521 .iter()
6522 .find(|data| data.reference.is_local() && data.reference.name == *segment)
6523 else {
6524 self.errors.push(
6525 self.engine_error(format!("Data '{}' not found", segment), reference_source),
6526 );
6527 return None;
6528 };
6529
6530 if let ParsedDataValue::Import {
6531 spec_ref,
6532 effective_ref: Some(_),
6533 ..
6534 } = &data_ref.value
6535 {
6536 let targets = match self.dynamic_import_targets(
6540 spec_ref,
6541 spec_context,
6542 &context_repository,
6543 reference_source,
6544 ) {
6545 Ok(targets) => targets,
6546 Err(e) => {
6547 self.errors.push(e);
6548 return None;
6549 }
6550 };
6551 let first = targets
6552 .into_iter()
6553 .next()
6554 .expect("BUG: dynamic_import_targets returns at least one target");
6555 walk_effective = first.at;
6556 path_segments.push(PathSegment {
6557 uses: Arc::from(segment.as_str()),
6558 row: None,
6559 repository: first.repository.name.as_deref().map(Arc::from),
6560 spec: Arc::from(first.spec.name.as_str()),
6561 });
6562 last_arc = Some(first.spec);
6563 spec_context = first.spec;
6564 context_repository = first.repository;
6565 } else if let ParsedDataValue::Import { spec_ref, .. } = &data_ref.value {
6566 let (repo, arc) = match self.resolve_spec_ref(
6567 spec_ref,
6568 &walk_effective,
6569 spec_context,
6570 &context_repository,
6571 ) {
6572 Ok(pair) => pair,
6573 Err(e) => {
6574 self.errors.push(e);
6575 return None;
6576 }
6577 };
6578 walk_effective = spec_ref.at(&walk_effective);
6579 path_segments.push(PathSegment {
6580 uses: Arc::from(segment.as_str()),
6581 row: None,
6582 repository: repo.name.as_deref().map(Arc::from),
6583 spec: Arc::from(arc.name.as_str()),
6584 });
6585 last_arc = Some(arc);
6586 spec_context = arc;
6587 context_repository = repo;
6588 } else if matches!(
6589 &data_ref.value,
6590 ParsedDataValue::Definition { list: Some(_), .. }
6591 ) {
6592 self.errors.push(self.engine_error(
6593 format!(
6594 "`{segment}` is a list: there is no `{segment}.<field>` outside a command argument; \
6595 pick a row first, for example `({segment} -> first).<field>`"
6596 ),
6597 reference_source,
6598 ));
6599 return None;
6600 } else {
6601 self.errors.push(self.engine_error(
6602 format!("Data '{}' is not a spec reference", segment),
6603 reference_source,
6604 ));
6605 return None;
6606 }
6607 }
6608
6609 let final_arc = last_arc.unwrap_or_else(|| {
6610 unreachable!(
6611 "BUG: resolve_path_segments called with empty segments should not reach here"
6612 )
6613 });
6614 Some((path_segments, final_arc))
6615 }
6616
6617 fn ensure_spec_types_resolved(
6620 &mut self,
6621 spec: &'a LemmaSpec,
6622 spec_repository: &Arc<LemmaRepository>,
6623 effective: &EffectiveDate,
6624 type_resolver: &TypeResolver<'a>,
6625 ) -> bool {
6626 let root = match self.enter_spec_types(spec, spec_repository, effective, type_resolver) {
6627 Ok(frame) => frame,
6628 Err(resolved) => return resolved,
6629 };
6630 let mut stack = vec![root];
6631 loop {
6632 let mut frame = stack
6633 .pop()
6634 .expect("BUG: spec types walk ends when its root frame finishes");
6635 if let Some((child, child_repository, child_effective)) =
6636 self.next_spec_import(&mut frame)
6637 {
6638 stack.push(frame);
6639 if let Ok(child_frame) =
6640 self.enter_spec_types(child, &child_repository, &child_effective, type_resolver)
6641 {
6642 stack.push(child_frame);
6643 }
6644 continue;
6645 }
6646 let resolved = self.finish_spec_types(&frame, type_resolver);
6647 if stack.is_empty() {
6648 return resolved;
6649 }
6650 }
6651 }
6652
6653 fn enter_spec_types(
6656 &self,
6657 spec: &'a LemmaSpec,
6658 spec_repository: &Arc<LemmaRepository>,
6659 effective: &EffectiveDate,
6660 type_resolver: &TypeResolver<'a>,
6661 ) -> Result<SpecTypesFrame<'a>, bool> {
6662 if self
6663 .local_types
6664 .iter()
6665 .any(|(_, s, _)| discovery::same_loaded_spec(s, spec))
6666 {
6667 return Err(true);
6668 }
6669 if !type_resolver.is_registered(spec) {
6670 panic!(
6671 "BUG: spec '{}' reachable from spec '{}' was not registered during dependency discovery",
6672 spec.name, self.main_spec.name
6673 );
6674 }
6675 if type_resolver.registration_failed(spec) {
6676 return Err(false);
6679 }
6680 Ok(SpecTypesFrame {
6681 spec,
6682 repository: Arc::clone(spec_repository),
6683 effective: effective.clone(),
6684 next_data: 0,
6685 pending_targets: VecDeque::new(),
6686 })
6687 }
6688
6689 fn next_spec_import(
6692 &mut self,
6693 frame: &mut SpecTypesFrame<'a>,
6694 ) -> Option<(&'a LemmaSpec, Arc<LemmaRepository>, EffectiveDate)> {
6695 if let Some(dependency) = frame.pending_targets.pop_front() {
6696 return Some((dependency.spec, dependency.repository, dependency.at));
6697 }
6698 let spec = frame.spec;
6699 while let Some(data) = spec.data.get(frame.next_data) {
6700 frame.next_data += 1;
6701 if discovery::is_self_list(
6702 self.context,
6703 data,
6704 spec,
6705 &frame.repository,
6706 &frame.effective,
6707 ) {
6708 continue;
6709 }
6710 match &data.value {
6711 ParsedDataValue::ImportList { .. } => {
6712 panic!("BUG: uses list is rejected at parse and must not reach planning");
6713 }
6714 ParsedDataValue::Import {
6715 spec_ref,
6716 effective_ref: Some(_),
6717 ..
6718 } => {
6719 match self.dynamic_import_targets(
6720 spec_ref,
6721 spec,
6722 &frame.repository,
6723 &data.source_location,
6724 ) {
6725 Ok(targets) => {
6726 frame.pending_targets.extend(targets);
6727 if let Some(dependency) = frame.pending_targets.pop_front() {
6728 return Some((
6729 dependency.spec,
6730 dependency.repository,
6731 dependency.at,
6732 ));
6733 }
6734 }
6735 Err(error) => self.errors.push(error),
6736 }
6737 }
6738 ParsedDataValue::Import { spec_ref, .. } => {
6739 let import_effective = spec_ref.at(&frame.effective);
6740 match self.resolve_spec_ref(
6741 spec_ref,
6742 &import_effective,
6743 spec,
6744 &frame.repository,
6745 ) {
6746 Ok((source_repo, source_arc)) => {
6747 return Some((source_arc, source_repo, import_effective));
6748 }
6749 Err(error) => self.errors.push(error),
6750 }
6751 }
6752 ParsedDataValue::Definition { .. } => {
6753 let Some(spec_ref) = discovery::data_instance_list_spec_ref(spec, data) else {
6754 continue;
6755 };
6756 let import_effective = spec_ref.at(&frame.effective);
6757 match self.resolve_spec_ref(
6758 &spec_ref,
6759 &import_effective,
6760 spec,
6761 &frame.repository,
6762 ) {
6763 Ok((source_repo, source_arc)) => {
6764 return Some((source_arc, source_repo, import_effective));
6765 }
6766 Err(error) => {
6767 if matches!(
6771 &data.value,
6772 ParsedDataValue::Definition { list: Some(_), .. }
6773 ) {
6774 self.errors.push(error);
6775 }
6776 }
6777 }
6778 }
6779 }
6780 }
6781 None
6782 }
6783
6784 fn finish_spec_types(
6786 &mut self,
6787 frame: &SpecTypesFrame<'a>,
6788 type_resolver: &TypeResolver<'a>,
6789 ) -> bool {
6790 match type_resolver.resolve_and_validate(frame.spec, &frame.effective, &self.local_types) {
6791 Ok(resolved_types) => {
6792 self.local_types
6793 .push((Arc::clone(&frame.repository), frame.spec, resolved_types));
6794 true
6795 }
6796 Err(errors) => {
6797 self.errors.extend(errors);
6798 false
6799 }
6800 }
6801 }
6802
6803 fn build_spec(
6807 &mut self,
6808 spec: &'a LemmaSpec,
6809 spec_repository: &Arc<LemmaRepository>,
6810 effective: &EffectiveDate,
6811 type_resolver: &TypeResolver<'a>,
6812 ) {
6813 self.process_meta_fields(spec);
6814 let bindings = match self.build_data_bindings(spec, spec_repository, effective) {
6815 Ok(bindings) => bindings,
6816 Err(errors) => {
6817 self.errors.extend(errors);
6818 IndexMap::new()
6819 }
6820 };
6821 if !self.ensure_spec_types_resolved(spec, spec_repository, effective, type_resolver) {
6822 return;
6823 }
6824
6825 let mut imports: HashMap<String, PlannedImport> = HashMap::new();
6826 for data in &spec.data {
6827 if !data.reference.is_local() || !matches!(data.value, ParsedDataValue::Import { .. }) {
6828 continue;
6829 }
6830 if let Some(import) = self.plan_import(data, spec, spec_repository, effective) {
6831 imports.entry(data.reference.name.clone()).or_insert(import);
6832 }
6833 }
6834 let bindings = self.resolve_instance_alias_bindings(bindings, spec, &imports);
6835
6836 for data in &spec.data {
6837 if !data.reference.is_local()
6838 || self.is_data_instance_list(data, spec, spec_repository, effective)
6839 {
6840 continue;
6841 }
6842 if matches!(data.value, ParsedDataValue::Import { .. }) {
6843 if let Some(planned) = imports.get(&data.reference.name) {
6844 self.add_import(data, planned, &bindings, spec);
6845 }
6846 continue;
6847 }
6848 self.add_data(data, spec, spec_repository, effective);
6849 }
6850 for data in &spec.data {
6851 if data.reference.is_local()
6852 && self.is_data_instance_list(data, spec, spec_repository, effective)
6853 {
6854 self.plan_instance_list(data, spec, spec_repository, effective, &bindings);
6855 }
6856 }
6857
6858 let data_map: HashMap<String, &LemmaData> = spec
6859 .data
6860 .iter()
6861 .map(|data| (data.reference.name.clone(), data))
6862 .collect();
6863 let rule_names: HashSet<&str> = spec.rules.iter().map(|r| r.name.as_str()).collect();
6864 for rule in &spec.rules {
6865 self.add_rule(
6866 rule,
6867 spec,
6868 spec_repository,
6869 &data_map,
6870 &rule_names,
6871 effective,
6872 );
6873 }
6874 }
6875
6876 fn dynamic_import_targets(
6879 &mut self,
6880 spec_ref: &ast::SpecRef,
6881 consumer: &LemmaSpec,
6882 consumer_repository: &Arc<LemmaRepository>,
6883 source: &Source,
6884 ) -> Result<Vec<discovery::ResolvedDependencyEdge<'a>>, Error> {
6885 let resolved = discovery::resolve_spec_ref_repository(
6886 self.context,
6887 spec_ref,
6888 Some(source),
6889 consumer.name.as_str(),
6890 Some(consumer),
6891 )?;
6892 let repository = match &resolved.repository {
6893 Some(explicit) => Arc::clone(explicit),
6894 None => Arc::clone(consumer_repository),
6895 };
6896 let Some(dep_set) = self.context.spec_set(&repository, &spec_ref.name) else {
6897 return Err(self.engine_error(
6898 format!(
6899 "dependency '{}' does not exist",
6900 discovery::format_dep_qualified_name(&repository, &spec_ref.name)
6901 ),
6902 source,
6903 ));
6904 };
6905 let mut targets = Vec::new();
6906 for version in dep_set.iter_specs() {
6907 let version_slices =
6908 match self
6909 .interners
6910 .slices
6911 .of(self.context, &repository, version, self.limits)
6912 {
6913 Ok(slices) => slices,
6914 Err(errors) => {
6915 self.errors.extend(errors);
6916 return Err(self.engine_error(
6917 format!(
6918 "failed to plan temporal slices of '{}'",
6919 discovery::format_dep_qualified_name(&repository, &spec_ref.name)
6920 ),
6921 source,
6922 ));
6923 }
6924 };
6925 for start in version_slices.starts() {
6926 if discovery::same_loaded_spec(version, consumer) {
6927 continue;
6928 }
6929 targets.push(discovery::ResolvedDependencyEdge {
6930 repository: Arc::clone(&repository),
6931 spec: version,
6932 at: start.clone(),
6933 });
6934 }
6935 }
6936 if targets.is_empty() {
6937 return Err(self.engine_error(
6938 format!(
6939 "'{}' depends on '{}' via `-> effective`, but that dependency has no temporal versions",
6940 consumer.name,
6941 discovery::format_dep_qualified_name(&repository, &spec_ref.name)
6942 ),
6943 source,
6944 ));
6945 }
6946 Ok(targets)
6947 }
6948
6949 fn plan_import(
6951 &mut self,
6952 data: &LemmaData,
6953 spec: &'a LemmaSpec,
6954 spec_repository: &Arc<LemmaRepository>,
6955 effective: &EffectiveDate,
6956 ) -> Option<PlannedImport> {
6957 let ParsedDataValue::Import {
6958 spec_ref,
6959 effective_ref,
6960 ..
6961 } = &data.value
6962 else {
6963 unreachable!("BUG: plan_import on non-import data");
6964 };
6965 let Some(pin_ref) = effective_ref else {
6966 let (repository, imported) =
6967 match self.resolve_spec_ref(spec_ref, effective, spec, spec_repository) {
6968 Ok(pair) => pair,
6969 Err(error) => {
6970 self.errors.push(error);
6971 return None;
6972 }
6973 };
6974 let at = spec_ref.at(effective);
6975 let identity = self.identity_at(&repository, imported, &at)?;
6976 let template = self.row_template(&identity, &repository, imported, &at)?;
6977 return Some(PlannedImport::Static {
6978 identity: Arc::new(identity),
6979 template,
6980 });
6981 };
6982
6983 if !pin_ref.segments.is_empty() {
6984 self.errors.push(self.engine_error(
6985 "`-> effective` names a local data or rule of this spec, not a path through an import"
6986 .to_string(),
6987 &data.source_location,
6988 ));
6989 return None;
6990 }
6991 if pin_ref.name == data.reference.name {
6992 self.errors.push(self.engine_error(
6993 format!(
6994 "`-> effective {}` cannot name the import alias '{}'",
6995 pin_ref.name, data.reference.name
6996 ),
6997 &data.source_location,
6998 ));
6999 return None;
7000 }
7001
7002 let pin = match self.resolve_effective_pin(pin_ref, spec, &data.source_location) {
7003 Ok(pin) => pin,
7004 Err(error) => {
7005 self.errors.push(error);
7006 return None;
7007 }
7008 };
7009
7010 let targets = match self.dynamic_import_targets(
7011 spec_ref,
7012 spec,
7013 spec_repository,
7014 &data.source_location,
7015 ) {
7016 Ok(targets) => targets,
7017 Err(error) => {
7018 self.errors.push(error);
7019 return None;
7020 }
7021 };
7022
7023 let mut candidates = Vec::new();
7024 for target in targets {
7025 let identity = self.identity_at(&target.repository, target.spec, &target.at)?;
7026 let template =
7027 self.row_template(&identity, &target.repository, target.spec, &target.at)?;
7028 candidates.push((Arc::new(identity), template));
7029 }
7030 Some(PlannedImport::Dynamic { pin, candidates })
7031 }
7032
7033 fn resolve_effective_pin(
7036 &self,
7037 pin_ref: &ast::Reference,
7038 spec: &LemmaSpec,
7039 source: &Source,
7040 ) -> Result<ImportEffectivePin, Error> {
7041 if spec
7042 .data
7043 .iter()
7044 .any(|data| data.reference.is_local() && data.reference.name == pin_ref.name)
7045 {
7046 return Ok(ImportEffectivePin::Data(DataPath::local(
7047 pin_ref.name.clone(),
7048 )));
7049 }
7050 if spec.rules.iter().any(|rule| rule.name == pin_ref.name) {
7051 return Ok(ImportEffectivePin::Rule(RulePath::new(
7052 Vec::new(),
7053 pin_ref.name.clone(),
7054 )));
7055 }
7056 Err(self.engine_error(
7057 format!(
7058 "`-> effective {}` does not name data or a rule of '{}'",
7059 pin_ref.name, spec.name
7060 ),
7061 source,
7062 ))
7063 }
7064
7065 fn add_import(
7068 &mut self,
7069 data: &LemmaData,
7070 planned: &PlannedImport,
7071 bindings: &DataBindings,
7072 spec: &LemmaSpec,
7073 ) {
7074 let alias = &data.reference.name;
7075 let data_path = DataPath::local(alias.clone());
7076 if self.is_duplicate_data(data, &data_path, spec) {
7077 return;
7078 }
7079 let under = bindings_under(bindings, alias);
7080 match planned {
7081 PlannedImport::Static { identity, template } => {
7082 let prefix = vec![PathSegment::import(alias, identity)];
7083 let row = self.binding_row(RowWrite {
7084 base: vec![None; template.data.len()],
7085 identity: Arc::clone(identity),
7086 template: Arc::clone(template),
7087 origin: RowOrigin::Import {
7088 alias: Arc::from(alias.as_str()),
7089 },
7090 bindings: under,
7091 prefix,
7092 hops: 0,
7093 });
7094 self.data.insert(
7095 data_path,
7096 DataDefinition::Import {
7097 row,
7098 pin: None,
7099 candidates: BTreeMap::new(),
7100 source: data.source_location.clone(),
7101 },
7102 );
7103 }
7104 PlannedImport::Dynamic { pin, candidates } => {
7105 if let Err(error) =
7106 self.validate_effective_pin_type(pin, spec, &data.source_location)
7107 {
7108 self.errors.push(error);
7109 return;
7110 }
7111 let mut candidate_rows = BTreeMap::new();
7112 for (identity, template) in candidates {
7113 for binding in under.keys() {
7114 let slot = binding.last().expect("BUG: binding key is never empty");
7115 let path = DataPath::local(slot.clone());
7116 if !template.data.contains_key(&path)
7117 && !template
7118 .data
7119 .keys()
7120 .any(|data_path| data_path.input_key() == *slot)
7121 {
7122 }
7124 }
7125 let prefix = vec![PathSegment::import(alias, identity)];
7126 let row = self.binding_row(RowWrite {
7127 base: vec![None; template.data.len()],
7128 identity: Arc::clone(identity),
7129 template: Arc::clone(template),
7130 origin: RowOrigin::Import {
7131 alias: Arc::from(alias.as_str()),
7132 },
7133 bindings: under.clone(),
7134 prefix,
7135 hops: 0,
7136 });
7137 candidate_rows.insert(identity.as_ref().clone(), row);
7138 }
7139 let row = Arc::clone(
7140 candidate_rows
7141 .values()
7142 .next()
7143 .expect("BUG: dynamic import candidates are non-empty"),
7144 );
7145 self.data.insert(
7146 data_path,
7147 DataDefinition::Import {
7148 row,
7149 pin: Some(pin.clone()),
7150 candidates: candidate_rows,
7151 source: data.source_location.clone(),
7152 },
7153 );
7154 }
7155 }
7156 }
7157
7158 fn validate_effective_pin_type(
7159 &self,
7160 pin: &ImportEffectivePin,
7161 spec: &LemmaSpec,
7162 source: &Source,
7163 ) -> Result<(), Error> {
7164 let lemma_type: Option<Arc<LemmaType>> = match pin {
7165 ImportEffectivePin::Data(path) => self
7166 .data
7167 .get(path)
7168 .and_then(|definition| definition.resolved_type_arc().map(Arc::clone))
7169 .or_else(|| {
7170 let name = &path.data;
7172 spec.data.iter().find_map(|data| {
7173 if data.reference.name != *name {
7174 return None;
7175 }
7176 match &data.value {
7177 ParsedDataValue::Definition {
7178 base:
7179 Some(ParentType::Primitive {
7180 primitive: PrimitiveKind::Date,
7181 }),
7182 ..
7183 } => Some(Arc::clone(semantics::primitive_date_arc())),
7184 _ => None,
7185 }
7186 })
7187 }),
7188 ImportEffectivePin::Rule(path) => {
7189 self.rules.get(path).map(|rule| Arc::clone(&rule.rule_type))
7190 }
7191 };
7192 let Some(lemma_type) = lemma_type else {
7193 if matches!(pin, ImportEffectivePin::Rule(_)) {
7196 return Ok(());
7197 }
7198 return Err(self.engine_error(
7199 "`-> effective` must name date or datetime data of this spec".to_string(),
7200 source,
7201 ));
7202 };
7203 if lemma_type.is_date() {
7204 return Ok(());
7205 }
7206 Err(self.engine_error(
7207 format!(
7208 "`-> effective` requires a date or datetime, got {}",
7209 lemma_type.specifications
7210 ),
7211 source,
7212 ))
7213 }
7214
7215 fn binding_row(&mut self, write: RowWrite) -> Arc<RowInstance> {
7220 struct PendingList {
7222 slot: usize,
7223 rows: Vec<Arc<RowInstance>>,
7224 row_type: Arc<LemmaType>,
7225 key: ListKey,
7226 template: Arc<ExecutionPlan>,
7227 source: Source,
7228 }
7229
7230 struct Pending {
7231 identity: Arc<SpecIdentity>,
7232 origin: RowOrigin,
7233 hops: u32,
7234 bindings: Vec<Option<RowBinding>>,
7235 lists: Vec<PendingList>,
7236 parent: Option<(usize, usize)>,
7238 }
7239
7240 let mut pending: Vec<Pending> = Vec::new();
7241 let mut writes: Vec<(RowWrite, Option<(usize, usize)>)> = vec![(write, None)];
7242 while let Some((write, parent)) = writes.pop() {
7243 let row = pending.len();
7244 let mut bindings = write.base;
7245 let mut lists = Vec::new();
7246 let mut nested: IndexMap<String, DataBindings> = IndexMap::new();
7247 for (key, (value, source)) in &write.bindings {
7248 let (name, rest) = key.split_first().expect("BUG: binding key is never empty");
7249 let path = DataPath::local(name.clone());
7250 let (slot, _, definition) =
7251 write.template.data.get_full(&path).unwrap_or_else(|| {
7252 panic!(
7253 "BUG: binding `{}` is not data of `{}`",
7254 key.join("."),
7255 write.template.identity.spec
7256 )
7257 });
7258 if !rest.is_empty() {
7259 assert!(
7260 matches!(definition, DataDefinition::Import { .. }),
7261 "BUG: binding `{}` steps through `{name}`, which is not a `uses`",
7262 key.join(".")
7263 );
7264 nested
7265 .entry(name.clone())
7266 .or_default()
7267 .insert(rest.to_vec(), (value.clone(), source.clone()));
7268 continue;
7269 }
7270 if matches!(definition, DataDefinition::Import { .. }) {
7271 let bound = write
7272 .prefix
7273 .iter()
7274 .map(PathSegment::input_key)
7275 .chain(std::iter::once(name.clone()))
7276 .collect::<Vec<_>>()
7277 .join(".");
7278 self.errors.push(self.engine_error(
7279 format!("cannot bind import alias '{bound}' as a value"),
7280 source,
7281 ));
7282 continue;
7283 }
7284 match value {
7285 BindingValue::Literal(literal) => {
7286 let field_type = definition
7287 .resolved_type_arc()
7288 .expect("BUG: a value slot of a template plan is typed");
7289 match source_row_literal(literal, field_type, &write.template) {
7290 Ok(bound) => {
7291 bindings[slot] =
7292 Some(RowBinding::Bound(OperationResult::from_bound(bound)));
7293 }
7294 Err(message) => {
7295 self.errors.push(self.engine_error(message, source));
7296 }
7297 }
7298 }
7299 BindingValue::Reference { target, .. } => {
7300 bindings[slot] = Some(RowBinding::Deferred {
7301 target: target.clone(),
7302 hops: write.hops,
7303 });
7304 }
7305 BindingValue::InstanceRows(copies) => {
7306 let (nested_identity, nested_template) =
7307 self.nested_row_template(&write.template, &path);
7308 let row_type = Arc::new(LemmaType::primitive(TypeSpecification::Spec {
7309 spec: nested_identity.as_ref().clone(),
7310 }));
7311 lists.push(PendingList {
7312 slot,
7313 rows: Vec::new(),
7314 row_type,
7315 key: write
7316 .template
7317 .instance_lists
7318 .get(&path)
7319 .expect(
7320 "BUG: a list binding slot is an instance list of its template",
7321 )
7322 .key
7323 .clone(),
7324 template: Arc::clone(&nested_template),
7325 source: source.clone(),
7326 });
7327 let list_name: Arc<str> = Arc::from(name.as_str());
7328 let spec_name: Arc<str> = Arc::from(nested_identity.spec.as_str());
7329 let repository: Option<Arc<str>> =
7330 nested_identity.repository.as_deref().map(Arc::from);
7331 for (index, copy) in copies.iter().enumerate() {
7332 if copy.identity != *nested_identity {
7333 self.errors.push(self.engine_error(
7334 format!(
7335 "`{}` is a `{}` instance; rows of `{name}` are `{}`",
7336 copy.name, copy.identity, nested_identity
7337 ),
7338 ©.source,
7339 ));
7340 continue;
7341 }
7342 let segment = PathSegment::list_row_shared(
7343 &list_name,
7344 index + 1,
7345 &spec_name,
7346 repository.as_ref(),
7347 );
7348 let prefix: Vec<PathSegment> =
7349 write.prefix.iter().cloned().chain([segment]).collect();
7350 writes.push((
7351 RowWrite {
7352 identity: Arc::clone(&nested_identity),
7353 base: vec![None; nested_template.data.len()],
7354 template: Arc::clone(&nested_template),
7355 origin: RowOrigin::Written {
7356 instance: SpecInstance {
7357 prefix: prefix.clone(),
7358 },
7359 source: copy.source.clone(),
7360 },
7361 bindings: copy.bindings.clone(),
7362 prefix,
7363 hops: 0,
7364 },
7365 Some((row, slot)),
7366 ));
7367 }
7368 }
7369 BindingValue::InstanceAliases(_) => unreachable!(
7370 "BUG: instance aliases are resolved to rows before rows are built"
7371 ),
7372 }
7373 }
7374 for (name, sub) in nested {
7375 let path = DataPath::local(name.clone());
7376 let (slot, _, definition) = write
7377 .template
7378 .data
7379 .get_full(&path)
7380 .expect("BUG: nested binding slot is template data");
7381 let DataDefinition::Import {
7382 row: template_row, ..
7383 } = definition
7384 else {
7385 unreachable!("BUG: nested binding slot is a `uses`");
7386 };
7387 let base = match &bindings[slot] {
7388 Some(RowBinding::Instance(written)) => written.bindings.to_vec(),
7389 None => template_row.bindings.to_vec(),
7390 Some(other) => panic!("BUG: `uses` slot `{name}` holds {other:?}"),
7391 };
7392 let identity = Arc::clone(&template_row.identity);
7393 let template = self.template_of(&identity);
7394 let prefix: Vec<PathSegment> = write
7395 .prefix
7396 .iter()
7397 .cloned()
7398 .chain([PathSegment::import(&name, &identity)])
7399 .collect();
7400 writes.push((
7401 RowWrite {
7402 identity,
7403 template,
7404 origin: template_row.origin.clone(),
7405 base,
7406 bindings: sub,
7407 prefix,
7408 hops: write.hops + 1,
7409 },
7410 Some((row, slot)),
7411 ));
7412 }
7413 pending.push(Pending {
7414 identity: write.identity,
7415 origin: write.origin,
7416 hops: write.hops,
7417 bindings,
7418 lists,
7419 parent,
7420 });
7421 }
7422
7423 loop {
7426 let row = pending.pop().expect("BUG: the written row is pending");
7427 let mut bindings = row.bindings;
7428 for list in row.lists {
7429 let rows =
7430 match self.row_store_key(&list.rows, &list.key, &list.source, &list.template) {
7431 None => list.rows,
7432 Some(store_key) => match RowStore::keyed(list.rows, store_key) {
7433 Ok(store) => store.rows,
7434 Err((message, unkeyed)) => {
7435 self.errors.push(self.engine_error(message, &list.source));
7436 unkeyed.rows
7437 }
7438 },
7439 };
7440 bindings[list.slot] = Some(RowBinding::Rows {
7441 rows,
7442 row_type: list.row_type,
7443 hops: row.hops,
7444 });
7445 }
7446 let built = Arc::new(RowInstance {
7447 identity: row.identity,
7448 origin: row.origin,
7449 bindings: bindings.into(),
7450 });
7451 let Some((holder, slot)) = row.parent else {
7452 assert!(
7453 pending.is_empty(),
7454 "BUG: only the written row has no holder"
7455 );
7456 return built;
7457 };
7458 let holder = &mut pending[holder];
7459 match holder.lists.iter_mut().find(|list| list.slot == slot) {
7460 Some(list) => list.rows.push(built),
7461 None => holder.bindings[slot] = Some(RowBinding::Instance(built)),
7462 }
7463 }
7464 }
7465
7466 fn template_of(&self, identity: &SpecIdentity) -> Arc<ExecutionPlan> {
7468 Arc::clone(
7469 self.instance_lists
7470 .templates
7471 .get(identity)
7472 .unwrap_or_else(|| {
7473 panic!(
7474 "BUG: template of `{identity}` is recorded with the template importing it"
7475 )
7476 }),
7477 )
7478 }
7479
7480 fn resolve_instance_alias_bindings(
7488 &mut self,
7489 bindings: DataBindings,
7490 spec: &LemmaSpec,
7491 imports: &HashMap<String, PlannedImport>,
7492 ) -> DataBindings {
7493 let mut resolved_aliases: HashMap<String, DataBindings> = HashMap::new();
7494 let mut visiting: Vec<String> = Vec::new();
7495 let mut stack: Vec<(String, bool)> = aliases_named(&bindings)
7497 .into_iter()
7498 .rev()
7499 .map(|alias| (alias, false))
7500 .collect();
7501 while let Some((alias, entered)) = stack.last().cloned() {
7502 if entered {
7503 stack.pop();
7504 visiting.pop();
7505 let own = bindings_under(&bindings, &alias);
7506 let resolved = self.resolve_alias_layer(own, spec, imports, &resolved_aliases);
7507 resolved_aliases.insert(alias, resolved);
7508 continue;
7509 }
7510 if resolved_aliases.contains_key(&alias) {
7511 stack.pop();
7512 continue;
7513 }
7514 if visiting.contains(&alias) {
7515 let chain: Vec<&str> = visiting
7516 .iter()
7517 .skip_while(|seen| **seen != alias)
7518 .map(String::as_str)
7519 .chain(std::iter::once(alias.as_str()))
7520 .collect();
7521 let source = alias_binding_source(&bindings, &alias)
7522 .expect("BUG: an alias on the resolution stack is named by a binding");
7523 self.errors.push(self.engine_error(
7524 format!(
7525 "Circular instance-list source wiring: {}",
7526 chain.join(" -> ")
7527 ),
7528 &source,
7529 ));
7530 resolved_aliases.insert(alias, IndexMap::new());
7531 stack.pop();
7532 continue;
7533 }
7534 stack.last_mut().expect("BUG: stack top cloned above").1 = true;
7535 visiting.push(alias.clone());
7536 let dependencies = aliases_named(&bindings_under(&bindings, &alias));
7537 for dependency in dependencies.into_iter().rev() {
7538 if !resolved_aliases.contains_key(&dependency) {
7539 stack.push((dependency, false));
7540 }
7541 }
7542 }
7543 self.resolve_alias_layer(bindings, spec, imports, &resolved_aliases)
7544 }
7545
7546 fn resolve_alias_layer(
7549 &mut self,
7550 bindings: DataBindings,
7551 spec: &LemmaSpec,
7552 imports: &HashMap<String, PlannedImport>,
7553 resolved_aliases: &HashMap<String, DataBindings>,
7554 ) -> DataBindings {
7555 let mut resolved: DataBindings = IndexMap::with_capacity(bindings.len());
7556 for (key, (value, source)) in bindings {
7557 let BindingValue::InstanceAliases(aliases) = &value else {
7558 resolved.insert(key, (value, source));
7559 continue;
7560 };
7561 let mut rows = Vec::with_capacity(aliases.len());
7562 let mut failed = false;
7563 for alias in aliases {
7564 let is_local_import = alias.segments.is_empty()
7565 && spec.data.iter().any(|data| {
7566 data.reference.is_local()
7567 && data.reference.name == alias.name
7568 && matches!(data.value, ParsedDataValue::Import { .. })
7569 });
7570 if !is_local_import {
7571 let slot = key.last().expect("BUG: binding key is never empty");
7572 self.errors.push(self.engine_error(
7573 format!(
7574 "binding `{}` replaces instance list `{slot}` with `{alias}`, which is not a `uses` instance of `{}`; list slots take `uses` aliases",
7575 key.join("."),
7576 spec.name
7577 ),
7578 &source,
7579 ));
7580 failed = true;
7581 continue;
7582 }
7583 let Some(planned) = imports.get(&alias.name) else {
7584 failed = true;
7586 continue;
7587 };
7588 if planned.is_dynamic() {
7589 self.errors.push(self.engine_error(
7590 format!(
7591 "cannot seal `uses {}` into a list: it uses `-> effective`",
7592 alias.name
7593 ),
7594 &source,
7595 ));
7596 failed = true;
7597 continue;
7598 }
7599 let identity = planned.primary_identity();
7600 let alias_bindings = resolved_aliases
7601 .get(&alias.name)
7602 .unwrap_or_else(|| {
7603 panic!(
7604 "BUG: alias `{}` named by a binding was not resolved",
7605 alias.name
7606 )
7607 })
7608 .clone();
7609 rows.push(RowCopy {
7610 identity: identity.as_ref().clone(),
7611 name: alias.name.clone(),
7612 bindings: alias_bindings,
7613 source: source.clone(),
7614 });
7615 }
7616 if failed {
7617 continue;
7618 }
7619 resolved.insert(key, (BindingValue::InstanceRows(rows), source));
7620 }
7621 resolved
7622 }
7623
7624 fn add_rule(
7625 &mut self,
7626 rule: &LemmaRule,
7627 current_spec: &'a LemmaSpec,
7628 current_repository: &Arc<LemmaRepository>,
7629 data_map: &HashMap<String, &LemmaData>,
7630 rule_names: &HashSet<&str>,
7631 effective: &EffectiveDate,
7632 ) {
7633 let rule_path = RulePath::new(Vec::new(), rule.name.clone());
7634
7635 if self.rules.contains_key(&rule_path) {
7636 let rule_source = &rule.source_location;
7637 self.errors.push(
7638 self.engine_error(format!("Duplicate rule '{}'", rule_path.rule), rule_source),
7639 );
7640 return;
7641 }
7642
7643 let piecewise_nodes = if rule.unless_clauses.is_empty() { 0 } else { 2 };
7644 let ast_nodes = rule.expression.table.len()
7645 + rule
7646 .unless_clauses
7647 .iter()
7648 .map(|clause| clause.condition.table.len() + clause.result.table.len())
7649 .sum::<usize>();
7650 let mut depends_on_rules = BTreeSet::new();
7651 let mut convert_ctx = RuleExpressionConversion {
7652 table: ExprTable::with_capacity(ast_nodes + piecewise_nodes),
7653 spec: current_spec,
7654 repository: current_repository,
7655 data_map,
7656 rule_names,
7657 rule: &rule.name,
7658 effective,
7659 depends_on_rules: &mut depends_on_rules,
7660 row_scopes: Vec::new(),
7661 };
7662
7663 let mut scratch = ConvertScratch {
7664 frames: Vec::with_capacity(ast_nodes),
7665 values: Vec::with_capacity(ast_nodes),
7666 children: Vec::new(),
7667 };
7668 let errors_before = self.errors.len();
7669 let Some(default_result) =
7670 self.convert_ast_expression(rule.expression.root(), &mut convert_ctx, &mut scratch)
7671 else {
7672 self.drop_unconverted_rule(rule_path, errors_before);
7673 return;
7674 };
7675 let mut branches = vec![(None, default_result)];
7676 let default_source = convert_ctx.table.source(default_result).cloned();
7677 let mut arms = Vec::with_capacity(rule.unless_clauses.len() + 1);
7678 if !rule.unless_clauses.is_empty() {
7679 let default_condition = convert_ctx.push(
7680 ExpressionKind::Literal(Box::new(TypedLiteral::from_bool(true))),
7681 default_source.clone(),
7682 );
7683 arms.push((default_condition, default_result));
7684 }
7685 for unless_clause in &rule.unless_clauses {
7686 let Some(condition) = self.convert_ast_expression(
7687 unless_clause.condition.root(),
7688 &mut convert_ctx,
7689 &mut scratch,
7690 ) else {
7691 self.drop_unconverted_rule(rule_path, errors_before);
7692 return;
7693 };
7694 let Some(result) = self.convert_ast_expression(
7695 unless_clause.result.root(),
7696 &mut convert_ctx,
7697 &mut scratch,
7698 ) else {
7699 self.drop_unconverted_rule(rule_path, errors_before);
7700 return;
7701 };
7702 branches.push((Some(condition), result));
7703 arms.push((condition, result));
7704 }
7705 let root = if arms.is_empty() {
7706 default_result
7707 } else {
7708 convert_ctx.push(ExpressionKind::Piecewise(arms), default_source)
7709 };
7710
7711 let expression = ExprTree::new(std::mem::take(&mut convert_ctx.table), root);
7712 let rule_node = RuleNode {
7713 expression,
7714 branches,
7715 source: rule.source_location.clone(),
7716 depends_on_rules,
7717 recursive: false,
7718 rule_type: Arc::new(LemmaType::veto_type()),
7719 scale_families: BTreeSet::new(),
7720 spec: current_spec,
7721 };
7722
7723 self.rules.insert(rule_path, rule_node);
7724 }
7725
7726 fn mark_self_list_row_rules_recursive(&mut self) {
7728 let targets: Vec<RulePath> = self.self_list_row_rules.iter().cloned().collect();
7729 for path in targets {
7730 if self.rules.contains_key(&path) {
7731 self.rules
7732 .get_mut(&path)
7733 .expect("BUG: self-list row rule missing from graph after contains_key")
7734 .recursive = true;
7735 } else if self.dropped_rules.contains(&path) {
7736 continue;
7737 } else {
7738 panic!(
7739 "BUG: self-list row rule '{path}' neither in graph nor dropped after conversion"
7740 )
7741 }
7742 }
7743 }
7744
7745 fn drop_unconverted_rule(&mut self, rule_path: RulePath, errors_before: usize) {
7746 if self.errors.len() <= errors_before {
7747 panic!("BUG: add_rule conversion failed without an error");
7748 }
7749 self.dropped_rules.insert(rule_path);
7750 }
7751
7752 fn convert_ast_expression<'t>(
7756 &mut self,
7757 expr: AstRef<'t>,
7758 ctx: &mut RuleExpressionConversion<'a, '_>,
7759 scratch: &mut ConvertScratch<'t>,
7760 ) -> Option<ExprId> {
7761 let ConvertScratch {
7762 frames,
7763 values,
7764 children,
7765 } = scratch;
7766 frames.push(ConvertFrame::Enter(expr));
7767 while let Some(frame) = frames.pop() {
7768 match frame {
7769 ConvertFrame::Enter(node) => {
7770 frames.push(ConvertFrame::Node(node));
7771 let start = frames.len();
7772 for_each_conversion_child(node.kind(), |child| {
7773 frames.push(ConvertFrame::Enter(node.child(child)));
7774 });
7775 frames[start..].reverse();
7776 }
7777 ConvertFrame::Node(node) => {
7778 let mut arity = 0;
7779 for_each_conversion_child(node.kind(), |_| arity += 1);
7780 children.clear();
7781 let mut converted = true;
7782 for value in values.drain(values.len() - arity..) {
7783 match value {
7784 Some(id) => children.push(id),
7785 None => converted = false,
7786 }
7787 }
7788 if !converted {
7789 values.push(None);
7790 } else if let ast::ExpressionKind::ListCommand { .. } = node.kind() {
7791 self.start_list_command(node, one(children), ctx, frames);
7792 } else {
7793 values.push(self.convert_ast_node(node, children, ctx));
7794 }
7795 }
7796 ConvertFrame::List(build) => {
7797 self.advance_list_command(build, frames, values, ctx);
7798 }
7799 ConvertFrame::ListArgument(mut build, sink) => {
7800 let converted = values
7801 .pop()
7802 .expect("BUG: list argument is converted before its frame resumes");
7803 match (sink, converted) {
7804 (ListArgumentSink::Positional, Some(id)) => build.converted.push(id),
7805 (ListArgumentSink::Positional, None) => {
7806 values.push(None);
7807 continue;
7808 }
7809 (ListArgumentSink::Field(path), Some(id)) => build.fields.push((path, id)),
7810 (ListArgumentSink::Field(_), None) => build.failed = true,
7811 }
7812 frames.push(ConvertFrame::List(build));
7813 }
7814 ConvertFrame::PopRowScope => {
7815 ctx.row_scopes.pop();
7816 }
7817 }
7818 }
7819 let converted = values
7820 .pop()
7821 .expect("BUG: conversion leaves one result for its root");
7822 assert!(
7823 values.is_empty(),
7824 "BUG: conversion left operands without a parent"
7825 );
7826 converted
7827 }
7828
7829 fn start_list_command<'t>(
7832 &mut self,
7833 node: AstRef<'t>,
7834 list: ExprId,
7835 ctx: &RuleExpressionConversion<'a, '_>,
7836 frames: &mut Vec<ConvertFrame<'t>>,
7837 ) {
7838 let ast::ExpressionKind::ListCommand {
7839 list: list_source,
7840 command,
7841 } = node.kind()
7842 else {
7843 unreachable!("BUG: list command frame started for a non-list-command node")
7844 };
7845 let source = node
7846 .source()
7847 .expect("BUG: AST expression missing source location")
7848 .clone();
7849 let level = u32::try_from(ctx.row_scopes.len()).expect("BUG: row scope depth exceeds u32");
7850 let value_predicate = matches!(
7851 command,
7852 ast::ListCommand::Any {
7853 form: ast::ListPredicate::Value { .. }
7854 } | ast::ListCommand::All {
7855 form: ast::ListPredicate::Value { .. }
7856 } | ast::ListCommand::Where {
7857 form: ast::ListPredicate::Value { .. }
7858 }
7859 );
7860 let mut missing_row_template = false;
7861 let row_scope = if value_predicate {
7862 Some(RowScope::Value { level, list })
7863 } else {
7864 self.instance_list_identity(
7865 ExprRef {
7866 table: &ctx.table,
7867 id: list,
7868 },
7869 ctx.spec,
7870 )
7871 .and_then(|identity| {
7872 let Some(template) = self.row_template_of(&identity) else {
7873 self.errors
7874 .push(self.missing_row_template_error(&identity, &source));
7875 missing_row_template = true;
7876 return None;
7877 };
7878 let list_path = match node.child(*list_source).kind() {
7879 ast::ExpressionKind::Reference(reference) => Some(reference.full_path()),
7880 _ => None,
7881 };
7882 Some(RowScope::Instance {
7883 list_path,
7884 identity,
7885 template,
7886 level,
7887 })
7888 })
7889 };
7890 let row_level = row_scope.as_ref().map(RowScope::level);
7891 let pending = list_command_arguments(command, &row_scope);
7892 frames.push(ConvertFrame::List(Box::new(ListCommandBuild {
7893 node,
7894 source,
7895 list,
7896 row_level,
7897 row_scope,
7898 pending,
7899 converted: Vec::new(),
7900 fields: Vec::new(),
7901 failed: missing_row_template,
7902 })));
7903 }
7904
7905 fn advance_list_command<'t>(
7907 &mut self,
7908 mut build: Box<ListCommandBuild<'t>>,
7909 frames: &mut Vec<ConvertFrame<'t>>,
7910 values: &mut Vec<Option<ExprId>>,
7911 ctx: &mut RuleExpressionConversion<'a, '_>,
7912 ) {
7913 let node = build.node;
7914 let Some(argument) = build.pending.pop_front() else {
7915 let ast::ExpressionKind::ListCommand { command, .. } = node.kind() else {
7916 unreachable!("BUG: list command frame holds a list command node")
7917 };
7918 let op = self.finish_list_command(
7919 command,
7920 &build.row_scope,
7921 std::mem::take(&mut build.converted),
7922 std::mem::take(&mut build.fields),
7923 build.failed,
7924 &build.source,
7925 );
7926 let source = Some(build.source.clone());
7927 let converted = op.map(|op| {
7928 ctx.push(
7929 ExpressionKind::ListCommand {
7930 list: build.list,
7931 op,
7932 row_level: build.row_level,
7933 },
7934 source,
7935 )
7936 });
7937 values.push(converted);
7938 return;
7939 };
7940 match argument {
7941 ListArgument::Enclosing(child) => {
7942 frames.push(ConvertFrame::ListArgument(
7943 build,
7944 ListArgumentSink::Positional,
7945 ));
7946 frames.push(ConvertFrame::Enter(node.child(child)));
7947 }
7948 ListArgument::InRows(child) => {
7949 let scoped = build.row_scope.clone();
7950 frames.push(ConvertFrame::ListArgument(
7951 build,
7952 ListArgumentSink::Positional,
7953 ));
7954 if let Some(scope) = scoped {
7955 ctx.row_scopes.push(scope);
7956 frames.push(ConvertFrame::PopRowScope);
7957 }
7958 frames.push(ConvertFrame::Enter(node.child(child)));
7959 }
7960 ListArgument::Field { name, value } => match self.row_field_path(&build, &name) {
7961 Some(path) => {
7962 frames.push(ConvertFrame::ListArgument(
7963 build,
7964 ListArgumentSink::Field(path),
7965 ));
7966 frames.push(ConvertFrame::Enter(node.child(value)));
7967 }
7968 None => {
7969 build.failed = true;
7970 frames.push(ConvertFrame::List(build));
7971 }
7972 },
7973 }
7974 }
7975
7976 fn convert_ast_node(
7977 &mut self,
7978 expr: AstRef<'_>,
7979 children: &[ExprId],
7980 ctx: &mut RuleExpressionConversion<'a, '_>,
7981 ) -> Option<ExprId> {
7982 let expr_src = expr
7983 .source()
7984 .expect("BUG: AST expression missing source location");
7985 let source = expr.source().cloned();
7986 match expr.kind() {
7987 ast::ExpressionKind::ImplicitRowValue => {
7988 let scope = ctx
7989 .row_scopes
7990 .iter()
7991 .rev()
7992 .find_map(|scope| match scope {
7993 RowScope::Value { level, list } => Some((*level, *list)),
7994 RowScope::Instance { .. } => None,
7995 })
7996 .expect("BUG: implicit row value converted outside a value predicate");
7997 Some(ctx.push(
7998 ExpressionKind::RowValue {
7999 level: scope.0,
8000 list: scope.1,
8001 },
8002 source,
8003 ))
8004 }
8005 ast::ExpressionKind::Reference(r) => {
8006 let expr_source = expr_src;
8007 if !ctx.row_scopes.is_empty() {
8008 match self.resolve_row_field(r, expr_source, ctx) {
8009 RowFieldLookup::Field { field, member } => {
8010 let ExpressionKind::RowField { identity, end, .. } = field.as_ref()
8011 else {
8012 unreachable!("BUG: a row field lookup holds a RowField read");
8013 };
8014 self.note_row_end_read(identity, end);
8015 let field = ctx.push(*field, source.clone());
8016 let Some((path, name)) = member else {
8017 return Some(field);
8018 };
8019 return Some(ctx.push(
8020 ExpressionKind::SpecMember {
8021 base: field,
8022 path,
8023 name,
8024 },
8025 source.clone(),
8026 ));
8027 }
8028 RowFieldLookup::Failed => return None,
8029 RowFieldLookup::NotARowField => {}
8030 }
8031 }
8032 if let Some((head, rest)) = r.segments.split_first() {
8033 if ctx.rule_names.contains(head.as_str()) && !ctx.data_map.contains_key(head) {
8034 let base = RulePath::new(Vec::new(), head.clone());
8035 ctx.depends_on_rules.insert(base.clone());
8036 let base = ctx.push(ExpressionKind::RulePath(base), source.clone());
8037 self.note_row_member_read(base, rest, &r.name, ctx);
8038 return Some(ctx.push(
8039 ExpressionKind::SpecMember {
8040 base,
8041 path: rest.to_vec(),
8042 name: r.name.clone(),
8043 },
8044 source.clone(),
8045 ));
8046 }
8047 }
8048 let target_arc_opt = if r.segments.is_empty() {
8049 None
8050 } else {
8051 let (_, arc) = self.resolve_path_segments(
8052 &r.segments,
8053 expr_source,
8054 Vec::new(),
8055 ctx.spec,
8056 ctx.repository,
8057 ctx.effective,
8058 )?;
8059 Some(arc)
8060 };
8061
8062 let (is_data, is_rule, target_spec_name_opt) = match &target_arc_opt {
8063 None => {
8064 let is_data = ctx.data_map.contains_key(&r.name);
8065 let is_rule = ctx.rule_names.contains(r.name.as_str());
8066 (is_data, is_rule, None)
8067 }
8068 Some(target_arc) => {
8069 let target_spec = target_arc;
8070 let target_data_names: HashSet<&str> = target_spec
8071 .data
8072 .iter()
8073 .filter(|f| f.reference.is_local())
8074 .map(|f| f.reference.name.as_str())
8075 .collect();
8076 let target_rule_names: HashSet<&str> =
8077 target_spec.rules.iter().map(|r| r.name.as_str()).collect();
8078 let is_data = target_data_names.contains(r.name.as_str());
8079 let is_rule = target_rule_names.contains(r.name.as_str());
8080 (is_data, is_rule, Some(target_spec.name.as_str()))
8081 }
8082 };
8083
8084 if is_data && is_rule {
8085 self.errors.push(self.engine_error(
8086 format!("'{}' is both a data and a rule", r.name),
8087 expr_source,
8088 ));
8089 return None;
8090 }
8091 if (is_data || is_rule) && !r.segments.is_empty() {
8092 let (head, rest) = r
8093 .segments
8094 .split_first()
8095 .expect("BUG: segments checked non-empty");
8096 let base = ctx.push(
8097 ExpressionKind::DataPath(DataPath::local(head.clone())),
8098 source.clone(),
8099 );
8100 return Some(ctx.push(
8101 ExpressionKind::SpecMember {
8102 base,
8103 path: rest.to_vec(),
8104 name: r.name.clone(),
8105 },
8106 source.clone(),
8107 ));
8108 }
8109 if is_data {
8110 let data_path = DataPath::local(r.name.clone());
8111 return Some(ctx.push(ExpressionKind::DataPath(data_path), source.clone()));
8112 }
8113 if is_rule {
8114 let rule_path = RulePath::new(Vec::new(), r.name.clone());
8115 ctx.depends_on_rules.insert(rule_path.clone());
8116 return Some(ctx.push(ExpressionKind::RulePath(rule_path), source.clone()));
8117 }
8118 let msg = match target_spec_name_opt {
8119 Some(s) => format!("Reference '{}' not found in spec '{}'", r.name, s),
8120 None => format!("Reference '{}' not found", r.name),
8121 };
8122 self.errors.push(self.engine_error(msg, expr_source));
8123 None
8124 }
8125
8126 ast::ExpressionKind::Logical {
8127 connective, steps, ..
8128 } => {
8129 let (first, steps) = convert_chain_steps(steps, children);
8130 Some(ctx.push(
8131 ExpressionKind::Logical {
8132 connective: *connective,
8133 first,
8134 steps,
8135 },
8136 source,
8137 ))
8138 }
8139
8140 ast::ExpressionKind::Arithmetic { steps, .. } => {
8141 let (first, steps) = convert_chain_steps(steps, children);
8142 Some(ctx.push(ExpressionKind::Arithmetic { first, steps }, source))
8143 }
8144
8145 ast::ExpressionKind::Comparison(_, op, _) => {
8146 let (left, right) = two(children);
8147 Some(ctx.push(ExpressionKind::Comparison(left, op.clone(), right), source))
8148 }
8149
8150 ast::ExpressionKind::UnitConversion(_, target) => {
8151 let converted_value = one(children);
8152
8153 let resolved_spec_types = self
8154 .local_types
8155 .iter()
8156 .find(|(_, s, _)| discovery::same_loaded_spec(s, ctx.spec))
8157 .map(|(_, _, t)| t);
8158 let unit_index = resolved_spec_types.map(|dt| &dt.unit_index);
8159 let resolved_map = resolved_spec_types.map(|dt| &dt.resolved);
8160 let semantic_target =
8161 match conversion_target_to_semantic(target, unit_index, resolved_map) {
8162 Ok(t) => t,
8163 Err(msg) => {
8164 let full_msg = unit_index
8165 .map(|idx| {
8166 let mut valid: Vec<&str> =
8167 idx.keys().map(String::as_str).collect();
8168 valid.sort_unstable();
8169 format!("{} Valid units: {}", msg, valid.join(", "))
8170 })
8171 .unwrap_or(msg);
8172 self.errors.push(Error::validation_with_context(
8173 full_msg,
8174 expr.source().cloned(),
8175 None::<String>,
8176 Some(self.main_spec),
8177 None,
8178 ));
8179 return None;
8180 }
8181 };
8182
8183 Some(ctx.push(
8184 ExpressionKind::UnitConversion(converted_value, semantic_target),
8185 source,
8186 ))
8187 }
8188
8189 ast::ExpressionKind::LogicalNegation(_, neg_type) => {
8190 let converted_operand = one(children);
8191 Some(ctx.push(
8192 ExpressionKind::LogicalNegation(converted_operand, neg_type.clone()),
8193 source,
8194 ))
8195 }
8196
8197 ast::ExpressionKind::MathematicalComputation(op, _) => {
8198 let converted_operand = one(children);
8199 Some(ctx.push(
8200 ExpressionKind::MathematicalComputation(op.clone(), converted_operand),
8201 source,
8202 ))
8203 }
8204
8205 ast::ExpressionKind::StepRounding { mode, .. } => {
8206 let mut children = children.iter();
8207 let operand = *children
8208 .next()
8209 .expect("BUG: step rounding always has an operand");
8210 let step = children.next().copied();
8211 Some(ctx.push(
8212 ExpressionKind::StepRounding {
8213 operand,
8214 mode: *mode,
8215 step,
8216 },
8217 source,
8218 ))
8219 }
8220
8221 ast::ExpressionKind::RangeBound(bound, _) => {
8222 let converted_operand = one(children);
8223 Some(ctx.push(
8224 ExpressionKind::RangeBound(*bound, converted_operand),
8225 source,
8226 ))
8227 }
8228
8229 ast::ExpressionKind::Literal(value) => {
8230 let semantic_value = match value {
8231 Value::NumberWithUnit(magnitude, unit) => {
8232 let (bare, lt) = match self.resolve_unit_ref(ctx.spec, unit) {
8233 Ok(resolved) => resolved,
8234 Err(message) => {
8235 self.errors.push(self.engine_error(message, expr_src));
8236 return None;
8237 }
8238 };
8239 match number_with_unit_to_value_kind(*magnitude, &bare, lt.as_ref()) {
8240 Ok(v) => v,
8241 Err(e) => {
8242 self.errors.push(self.engine_error(e, expr_src));
8243 return None;
8244 }
8245 }
8246 }
8247 Value::Range(left, right) => match (left.as_ref(), right.as_ref()) {
8248 (
8249 Value::NumberWithUnit(left_mag, left_unit),
8250 Value::NumberWithUnit(right_mag, right_unit),
8251 ) => {
8252 match self.measure_ratio_range_from_number_with_unit_endpoints(
8253 ctx.spec, *left_mag, left_unit, *right_mag, right_unit,
8254 ) {
8255 Ok(v) => v,
8256 Err(e) => {
8257 self.errors.push(self.engine_error(e, expr_src));
8258 return None;
8259 }
8260 }
8261 }
8262 _ => match value_to_semantic(value) {
8263 Ok(v) => v,
8264 Err(e) => {
8265 self.errors.push(self.engine_error(e, expr_src));
8266 return None;
8267 }
8268 },
8269 },
8270 _ => match value_to_semantic(value) {
8271 Ok(v) => v,
8272 Err(e) => {
8273 self.errors.push(self.engine_error(e, expr_src));
8274 return None;
8275 }
8276 },
8277 };
8278 let lemma_type: Arc<LemmaType> = match value {
8279 Value::Text(_) => primitive_text_arc().clone(),
8280 Value::Number(_) => primitive_number_arc().clone(),
8281 Value::NumberWithUnit(_, unit) => match self.resolve_unit_ref(ctx.spec, unit) {
8282 Ok((bare, lt)) => match &semantic_value {
8283 ValueKind::Measure(_) | ValueKind::Ratio(_) => {
8284 Arc::new(lt.as_ref().clone().with_unit(bare))
8285 }
8286 _ => lt,
8287 },
8288 Err(message) => {
8289 self.errors.push(self.engine_error(message, expr_src));
8290 return None;
8291 }
8292 },
8293 Value::Boolean(_) => primitive_boolean_arc().clone(),
8294 Value::Date(_) => primitive_date_arc().clone(),
8295 Value::Time(_) => primitive_time_arc().clone(),
8296 Value::Range(left_v, right_v) => match (left_v.as_ref(), right_v.as_ref()) {
8297 (Value::NumberWithUnit(_, _), Value::NumberWithUnit(_, _)) => {
8298 match &semantic_value {
8299 ValueKind::Range(left, right) => {
8300 let specs = range_type_specification_from_endpoints(
8301 left.lemma_type.as_ref(),
8302 right.lemma_type.as_ref(),
8303 )
8304 .unwrap_or_else(|| {
8305 unreachable!(
8306 "BUG: measure/ratio range endpoints must form a range type after resolve"
8307 )
8308 });
8309 Arc::new(LemmaType::primitive(specs))
8310 }
8311 _ => unreachable!(
8312 "BUG: semantic range literal conversion returned non-range value kind"
8313 ),
8314 }
8315 }
8316 _ => match &semantic_value {
8317 ValueKind::Range(left, right) => {
8318 let endpoint_type = |endpoint: &TypedLiteral| -> Arc<LemmaType> {
8319 match &endpoint.value {
8320 ValueKind::Number(_) => primitive_number_arc().clone(),
8321 ValueKind::Date(_) => primitive_date_arc().clone(),
8322 ValueKind::Time(_) => primitive_time_arc().clone(),
8323 ValueKind::Ratio(_) => {
8324 semantics::primitive_ratio_arc().clone()
8325 }
8326 ValueKind::Measure(_) => Arc::clone(&endpoint.lemma_type),
8327 other => unreachable!(
8328 "BUG: untyped range endpoint kind in expression conversion: {other:?}"
8329 ),
8330 }
8331 };
8332 let specs = range_type_specification_from_endpoints(
8333 endpoint_type(left).as_ref(),
8334 endpoint_type(right).as_ref(),
8335 )
8336 .unwrap_or_else(|| {
8337 unreachable!(
8338 "BUG: attempted to construct a range literal from incompatible endpoint types"
8339 )
8340 });
8341 Arc::new(LemmaType::primitive(specs))
8342 }
8343 _ => unreachable!(
8344 "BUG: semantic range literal conversion returned non-range value kind"
8345 ),
8346 },
8347 },
8348 };
8349 let literal_value = TypedLiteral {
8350 value: semantic_value,
8351 lemma_type,
8352 };
8353 Some(ctx.push(
8354 ExpressionKind::Literal(Box::new(literal_value)),
8355 source.clone(),
8356 ))
8357 }
8358
8359 ast::ExpressionKind::Veto(veto_expression) => {
8360 Some(ctx.push(ExpressionKind::Veto(veto_expression.clone()), source))
8361 }
8362
8363 ast::ExpressionKind::ResultIsVeto(_) => {
8364 let converted = one(children);
8365 Some(ctx.push(ExpressionKind::ResultIsVeto(converted), source))
8366 }
8367
8368 ast::ExpressionKind::Now => Some(ctx.push(ExpressionKind::Now, source)),
8369
8370 ast::ExpressionKind::DateRelative(kind, _) => {
8371 let converted_date = one(children);
8372 Some(ctx.push(ExpressionKind::DateRelative(*kind, converted_date), source))
8373 }
8374
8375 ast::ExpressionKind::DateCalendar(kind, unit, _) => {
8376 let converted_date = one(children);
8377 Some(ctx.push(
8378 ExpressionKind::DateCalendar(*kind, *unit, converted_date),
8379 source,
8380 ))
8381 }
8382
8383 ast::ExpressionKind::RangeLiteral(..) => {
8384 let (left, right) = two(children);
8385 Some(ctx.push(ExpressionKind::RangeLiteral(left, right), source))
8386 }
8387
8388 ast::ExpressionKind::PastFutureRange(kind, _) => {
8389 let converted_offset = one(children);
8390 Some(ctx.push(
8391 ExpressionKind::PastFutureRange(*kind, converted_offset),
8392 source,
8393 ))
8394 }
8395
8396 ast::ExpressionKind::RangeContainment(..) => {
8397 let (converted_value, converted_range) = two(children);
8398 Some(ctx.push(
8399 ExpressionKind::RangeContainment(converted_value, converted_range),
8400 source,
8401 ))
8402 }
8403
8404 ast::ExpressionKind::ListType { element } => {
8405 self.convert_list_type_expression(element, expr, ctx)
8406 }
8407
8408 ast::ExpressionKind::ListCommand { .. } => {
8409 unreachable!("BUG: list commands are converted by convert_ast_expression")
8410 }
8411
8412 ast::ExpressionKind::Member { path, .. } => {
8413 let converted_base = one(children);
8414 self.note_row_member_read(converted_base, &path.segments, &path.name, ctx);
8415 Some(ctx.push(
8416 ExpressionKind::SpecMember {
8417 base: converted_base,
8418 path: path.segments.clone(),
8419 name: path.name.clone(),
8420 },
8421 source,
8422 ))
8423 }
8424 }
8425 }
8426
8427 fn instance_list_identity(
8432 &self,
8433 list: ExprRef<'_, ExpressionKind>,
8434 spec: &LemmaSpec,
8435 ) -> Option<SpecIdentity> {
8436 let scope = SourceScope { spec };
8437 self.search_identity(
8438 IdentitySearch::new(SearchKind::List, Pending::Sem(list), Continuation::Answer),
8439 &scope,
8440 )
8441 }
8442
8443 fn row_value_identity<'e>(
8446 &'e self,
8447 expression: ExprRef<'e, ExpressionKind>,
8448 scope: &SourceScope<'e>,
8449 ) -> Option<SpecIdentity> {
8450 self.search_identity(
8451 IdentitySearch::new(
8452 SearchKind::Row,
8453 Pending::Sem(expression),
8454 Continuation::Answer,
8455 ),
8456 scope,
8457 )
8458 }
8459
8460 fn search_identity<'e>(
8463 &'e self,
8464 root: IdentitySearch<'e>,
8465 scope: &SourceScope<'e>,
8466 ) -> Option<SpecIdentity> {
8467 let mut searches = vec![root];
8468 while let Some(search) = searches.last_mut() {
8469 let Some(item) = search.pending.pop() else {
8470 searches.pop();
8471 continue;
8472 };
8473 let step = match search.kind {
8474 SearchKind::List => self.list_identity_step(item, scope, search),
8475 SearchKind::Row => self.row_identity_step(item, scope, search),
8476 };
8477 let mut found = match step {
8478 SearchStep::Next => continue,
8479 SearchStep::Nested(nested) => {
8480 searches.push(nested);
8481 continue;
8482 }
8483 SearchStep::Found(identity) => identity,
8484 };
8485 loop {
8486 let done = searches
8487 .pop()
8488 .expect("BUG: the search that found an identity is on the stack");
8489 match done.on_found {
8490 Continuation::Answer => return Some(found),
8491 Continuation::RowOfList => {}
8492 Continuation::Member { path, name } => {
8493 let parent = searches
8494 .last_mut()
8495 .expect("BUG: a member search nests in a list search");
8496 let chain = self.row_template_of(&found).and_then(|template| {
8497 let mut parts: Vec<&str> = path.iter().map(String::as_str).collect();
8498 parts.push(name);
8499 self.template_row_chain(&found, &template, &parts)
8500 });
8501 match chain.and_then(|chain| {
8502 let last = chain.last().expect("BUG: a row end chain has a last hop");
8503 self.row_end_list_identity(
8504 &last.identity,
8505 &last.end,
8506 &mut parent.pending,
8507 )
8508 }) {
8509 Some(identity) => found = identity,
8510 None => break,
8511 }
8512 }
8513 }
8514 }
8515 }
8516 None
8517 }
8518
8519 fn list_identity_step<'e>(
8522 &'e self,
8523 item: Pending<'e>,
8524 scope: &SourceScope<'e>,
8525 search: &mut IdentitySearch<'e>,
8526 ) -> SearchStep<'e> {
8527 let identity = match item {
8528 Pending::Sem(list) => match list.kind() {
8529 ExpressionKind::DataPath(path) => self.instance_list_data_identity(path),
8530 ExpressionKind::Literal(literal) => match &literal.lemma_type.specifications {
8531 TypeSpecification::List { element, .. } => match &element.specifications {
8532 TypeSpecification::Spec { spec } => Some(spec.clone()),
8533 _ => None,
8534 },
8535 _ => None,
8536 },
8537 ExpressionKind::ListCommand {
8538 list: inner, op, ..
8539 } => match op {
8540 ListOp::Predicate {
8541 kind: ListPredKind::Where,
8542 ..
8543 }
8544 | ListOp::Sort { .. }
8545 | ListOp::Top { .. }
8546 | ListOp::With { .. }
8547 | ListOp::WithRow { .. }
8548 | ListOp::Key { .. } => {
8549 search.pending.push(Pending::Sem(list.child(*inner)));
8550 None
8551 }
8552 ListOp::Predicate { .. }
8553 | ListOp::First
8554 | ListOp::Last
8555 | ListOp::Count
8556 | ListOp::Find { .. }
8557 | ListOp::Reduce { .. } => None,
8558 },
8559 ExpressionKind::RulePath(rule) => {
8560 search.pending.push(Pending::Rule(rule.clone()));
8561 None
8562 }
8563 ExpressionKind::Piecewise(arms) => {
8564 for (_, result) in arms.iter().rev() {
8565 search.pending.push(Pending::Sem(list.child(*result)));
8566 }
8567 None
8568 }
8569 ExpressionKind::RowField { identity, end, .. } => {
8570 self.row_end_list_identity(identity, end, &mut search.pending)
8571 }
8572 ExpressionKind::SpecMember {
8573 base, path, name, ..
8574 } => {
8575 return SearchStep::Nested(search.nested(
8576 SearchKind::Row,
8577 Pending::Sem(list.child(*base)),
8578 Continuation::Member { path, name },
8579 ));
8580 }
8581 _ => None,
8582 },
8583 Pending::Ast(expr) => match expr.kind() {
8584 ast::ExpressionKind::Reference(reference) if reference.segments.is_empty() => {
8585 let is_data = scope.spec.data.iter().any(|data| {
8586 data.reference.is_local() && data.reference.name == reference.name
8587 });
8588 if is_data {
8589 self.instance_list_data_identity(&DataPath::local(reference.name.clone()))
8590 } else {
8591 search.pending.push(Pending::Rule(RulePath::new(
8592 Vec::new(),
8593 reference.name.clone(),
8594 )));
8595 None
8596 }
8597 }
8598 ast::ExpressionKind::ListCommand {
8599 list,
8600 command:
8601 ast::ListCommand::Where { .. }
8602 | ast::ListCommand::Sort { .. }
8603 | ast::ListCommand::Top { .. }
8604 | ast::ListCommand::With { .. }
8605 | ast::ListCommand::Key { .. },
8606 } => {
8607 search.pending.push(Pending::Ast(expr.child(*list)));
8608 None
8609 }
8610 _ => None,
8611 },
8612 Pending::Rule(rule) => {
8613 if !search.enter(&rule) {
8614 None
8615 } else if let Some(node) = self.rules.get(&rule) {
8616 if let Some((_, body)) = node.branches.first() {
8617 search.pending.push(Pending::Sem(node.expression.at(*body)));
8618 }
8619 None
8620 } else if !rule.segments.is_empty() {
8621 None
8622 } else {
8623 if let Some(source_rule) = scope.spec.rules.iter().find(|r| r.name == rule.rule)
8624 {
8625 search
8626 .pending
8627 .push(Pending::Ast(source_rule.expression.root()));
8628 }
8629 None
8630 }
8631 }
8632 };
8633 match identity {
8634 Some(identity) => SearchStep::Found(identity),
8635 None => SearchStep::Next,
8636 }
8637 }
8638
8639 fn row_identity_step<'e>(
8642 &'e self,
8643 item: Pending<'e>,
8644 scope: &SourceScope<'e>,
8645 search: &mut IdentitySearch<'e>,
8646 ) -> SearchStep<'e> {
8647 match item {
8648 Pending::Sem(expression) => match expression.kind() {
8649 ExpressionKind::ListCommand {
8650 list,
8651 op: ListOp::First | ListOp::Last | ListOp::Find { .. },
8652 ..
8653 } => SearchStep::Nested(search.nested(
8654 SearchKind::List,
8655 Pending::Sem(expression.child(*list)),
8656 Continuation::RowOfList,
8657 )),
8658 ExpressionKind::RulePath(rule) => {
8659 search.pending.push(Pending::Rule(rule.clone()));
8660 SearchStep::Next
8661 }
8662 ExpressionKind::Piecewise(arms) => {
8663 for (_, result) in arms.iter().rev() {
8664 search.pending.push(Pending::Sem(expression.child(*result)));
8665 }
8666 SearchStep::Next
8667 }
8668 ExpressionKind::DataPath(path) => match self.data.get(path) {
8669 Some(DataDefinition::Import { row, .. }) => {
8670 SearchStep::Found(row.identity.as_ref().clone())
8671 }
8672 _ => SearchStep::Next,
8673 },
8674 _ => SearchStep::Next,
8675 },
8676 Pending::Ast(expr) => match expr.kind() {
8677 ast::ExpressionKind::Reference(reference) if reference.segments.is_empty() => {
8678 search.pending.push(Pending::Rule(RulePath::new(
8679 Vec::new(),
8680 reference.name.clone(),
8681 )));
8682 SearchStep::Next
8683 }
8684 ast::ExpressionKind::ListCommand {
8685 list,
8686 command:
8687 ast::ListCommand::First | ast::ListCommand::Last | ast::ListCommand::Find { .. },
8688 } => SearchStep::Nested(search.nested(
8689 SearchKind::List,
8690 Pending::Ast(expr.child(*list)),
8691 Continuation::RowOfList,
8692 )),
8693 _ => SearchStep::Next,
8694 },
8695 Pending::Rule(rule) => {
8696 if !search.enter(&rule) {
8697 } else if let Some(node) = self.rules.get(&rule) {
8698 for (_, body) in node.branches.iter().rev() {
8699 search.pending.push(Pending::Sem(node.expression.at(*body)));
8700 }
8701 } else if rule.segments.is_empty() {
8702 if let Some(source_rule) = scope.spec.rules.iter().find(|r| r.name == rule.rule)
8703 {
8704 for unless in source_rule.unless_clauses.iter().rev() {
8705 search.pending.push(Pending::Ast(unless.result.root()));
8706 }
8707 search
8708 .pending
8709 .push(Pending::Ast(source_rule.expression.root()));
8710 }
8711 }
8712 SearchStep::Next
8713 }
8714 }
8715 }
8716
8717 fn row_end_list_identity<'e>(
8720 &'e self,
8721 identity: &SpecIdentity,
8722 end: &RowEnd,
8723 pending: &mut Vec<Pending<'e>>,
8724 ) -> Option<SpecIdentity> {
8725 let own_rows = *identity == self.instance_lists.self_identity;
8726 let lemma_type: &LemmaType = match end {
8727 RowEnd::Data(field) if own_rows => self.data.get(field)?.resolved_type_arc()?,
8728 RowEnd::Data(field) => self
8729 .instance_lists
8730 .templates
8731 .get(identity)?
8732 .data
8733 .get(field)?
8734 .resolved_type_arc()?,
8735 RowEnd::Rule(rule) if own_rows => {
8736 pending.push(Pending::Rule(rule.clone()));
8737 return None;
8738 }
8739 RowEnd::Rule(rule) => {
8740 &self
8741 .instance_lists
8742 .templates
8743 .get(identity)?
8744 .rules
8745 .get(rule)?
8746 .rule_type
8747 }
8748 };
8749 if !lemma_type.is_list() {
8750 return None;
8751 }
8752 lemma_type
8753 .list_element_lemma_type()
8754 .spec_identity()
8755 .cloned()
8756 }
8757
8758 fn instance_list_data_identity(&self, path: &DataPath) -> Option<SpecIdentity> {
8759 let definition = self.data.get(path)?;
8760 match &definition.resolved_type_arc()?.specifications {
8761 TypeSpecification::List { element, .. } => match &element.specifications {
8762 TypeSpecification::Spec { spec } => Some(spec.clone()),
8763 _ => None,
8764 },
8765 _ => None,
8766 }
8767 }
8768
8769 fn note_row_member_read(
8772 &mut self,
8773 base: ExprId,
8774 path: &[String],
8775 name: &str,
8776 ctx: &mut RuleExpressionConversion<'a, '_>,
8777 ) {
8778 let scope = SourceScope { spec: ctx.spec };
8779 let base = ExprRef {
8780 table: &ctx.table,
8781 id: base,
8782 };
8783 let Some(identity) = self.row_value_identity(base, &scope) else {
8784 return;
8785 };
8786 if identity != self.instance_lists.self_identity {
8787 return;
8788 }
8789 let mut parts: Vec<&str> = path.iter().map(String::as_str).collect();
8790 parts.push(name);
8791 if let Some(chain) = self.template_row_chain(&identity, &RowTemplate::Root, &parts) {
8792 let first = chain.first().expect("BUG: a row end chain has a first hop");
8793 self.note_row_end_read(&first.identity, &first.end);
8794 }
8795 }
8796
8797 fn note_row_end_read(&mut self, identity: &SpecIdentity, end: &RowEnd) {
8800 match end {
8801 RowEnd::Rule(rule) => self.note_row_rule_read(identity, rule),
8802 RowEnd::Data(path) => {
8803 if let Some(DataDefinition::Reference {
8804 target: ReferenceTarget::Rule(rule),
8805 ..
8806 }) = self.data.get(path)
8807 {
8808 let rule = rule.clone();
8809 self.note_row_rule_read(identity, &rule);
8810 }
8811 }
8812 }
8813 }
8814
8815 fn note_row_rule_read(&mut self, identity: &SpecIdentity, rule: &RulePath) {
8820 if *identity != self.instance_lists.self_identity {
8821 return;
8822 }
8823 self.self_list_row_rules.insert(rule.clone());
8824 }
8825
8826 fn resolve_row_field(
8828 &mut self,
8829 r: &ast::Reference,
8830 source: &Source,
8831 ctx: &RuleExpressionConversion<'a, '_>,
8832 ) -> RowFieldLookup {
8833 let written = r.full_path();
8834 for scope in ctx.row_scopes.iter().rev() {
8835 let RowScope::Instance {
8836 list_path,
8837 identity,
8838 template,
8839 level,
8840 } = scope
8841 else {
8842 continue;
8843 };
8844 let Some(list_path) = list_path else {
8845 continue;
8846 };
8847 let Some(rest_parts) = written.strip_prefix(list_path.as_slice()) else {
8848 continue;
8849 };
8850 if rest_parts.is_empty() {
8851 continue;
8852 }
8853 let rest: Vec<&str> = rest_parts.iter().map(String::as_str).collect();
8854 let list_name = list_path.join(".");
8855 return match self.template_row_chain(identity, template, &rest) {
8856 Some(chain) => {
8857 row_field_lookup(*level, identity, chain, &rest, Some(list_path.clone()))
8858 }
8859 None => {
8860 let rest_display = rest.join(".");
8861 let message = format!(
8862 "no data, rule or uses `{rest_display}` on rows of `{list_name}` (`{}`)",
8863 identity.spec
8864 );
8865 self.errors.push(self.engine_error(message, source));
8866 RowFieldLookup::Failed
8867 }
8868 };
8869 }
8870 let field_parts: Vec<&str> = r
8871 .segments
8872 .iter()
8873 .map(String::as_str)
8874 .chain(std::iter::once(r.name.as_str()))
8875 .collect();
8876 let row_matches: Vec<(&RowScope, Vec<RowHop>)> = ctx
8877 .row_scopes
8878 .iter()
8879 .rev()
8880 .filter_map(|scope| {
8881 let RowScope::Instance {
8882 template, identity, ..
8883 } = scope
8884 else {
8885 return None;
8886 };
8887 self.template_row_chain(identity, template, &field_parts)
8888 .map(|chain| (scope, chain))
8889 })
8890 .collect();
8891 let Some((scope, chain)) = row_matches.first() else {
8892 return RowFieldLookup::NotARowField;
8893 };
8894 let head = r.segments.first().unwrap_or(&r.name);
8895 let enclosing = ctx.data_map.contains_key(head)
8896 || (ctx.rule_names.contains(head.as_str()) && head.as_str() != ctx.rule);
8897 if enclosing || row_matches.len() > 1 {
8898 let mut owners: Vec<String> = Vec::new();
8899 if enclosing {
8900 owners.push(format!("on `{}`", ctx.spec.name));
8901 }
8902 for (other, _) in &row_matches {
8903 let RowScope::Instance {
8904 list_path,
8905 identity,
8906 ..
8907 } = other
8908 else {
8909 unreachable!("BUG: row matches contain only instance scopes")
8910 };
8911 owners.push(match list_path {
8912 Some(path) => format!("on rows of `{}`", path.join(".")),
8913 None => format!("on `{}` rows", identity.spec),
8914 });
8915 }
8916 let RowScope::Instance {
8917 list_path,
8918 identity,
8919 ..
8920 } = scope
8921 else {
8922 unreachable!("BUG: row match is an instance scope")
8923 };
8924 let qualifier = list_path
8925 .as_ref()
8926 .map(|path| path.join("."))
8927 .unwrap_or_else(|| identity.spec.clone());
8928 let field = field_parts.join(".");
8929 self.errors.push(self.engine_error(
8930 format!(
8931 "`{field}` exists {}; write `{qualifier}.{field}`",
8932 owners.join(" and ")
8933 ),
8934 source,
8935 ));
8936 return RowFieldLookup::Failed;
8937 }
8938 let RowScope::Instance {
8939 identity, level, ..
8940 } = scope
8941 else {
8942 unreachable!("BUG: row match is an instance scope")
8943 };
8944 row_field_lookup(*level, identity, chain.clone(), &field_parts, None)
8945 }
8946
8947 fn convert_list_type_expression(
8948 &mut self,
8949 element: &ParentType,
8950 expr: AstRef<'_>,
8951 ctx: &mut RuleExpressionConversion<'a, '_>,
8952 ) -> Option<ExprId> {
8953 let (_, _, types) = self
8954 .local_types
8955 .iter()
8956 .find(|(_, spec, _)| discovery::same_loaded_spec(spec, ctx.spec))
8957 .expect("BUG: rule spec types resolved before rule conversion");
8958 let (_, list_type) = types
8959 .rule_list_types
8960 .iter()
8961 .find(|(written, _)| written == element)
8962 .expect("BUG: rule list types resolved with the spec's types");
8963 let list_type = Arc::clone(list_type);
8964 let source = expr
8965 .source()
8966 .expect("BUG: AST expression missing source location");
8967 if let TypeSpecification::List { element: row, .. } = &list_type.specifications {
8968 if let TypeSpecification::Spec { spec: identity } = &row.specifications {
8969 if *identity != self.instance_lists.self_identity && self.imports_in_scope(identity)
8973 {
8974 let spec_ref = discovery::list_element_spec_ref(ctx.spec, element)
8975 .expect("BUG: a rule list type of spec rows names the row spec");
8976 let (row_repository, row_spec) = match discovery::resolve_spec_ref_or_self(
8977 self.context,
8978 &spec_ref,
8979 ctx.repository,
8980 ctx.spec,
8981 ctx.effective,
8982 Some(source.clone()),
8983 ) {
8984 Ok(pair) => pair,
8985 Err(error) => {
8986 self.errors.push(error);
8987 return None;
8988 }
8989 };
8990 let at = spec_ref.at(ctx.effective);
8991 let resolved = self.identity_at(&row_repository, row_spec, &at)?;
8992 assert_eq!(
8993 resolved, *identity,
8994 "BUG: rule list type `{element}` resolved to another slice than its type"
8995 );
8996 self.row_template(identity, &row_repository, row_spec, &at)?;
8997 }
8998 }
8999 }
9000 Some(ctx.push(
9001 ExpressionKind::Literal(Box::new(TypedLiteral {
9002 value: ValueKind::List(Vec::new().into()),
9003 lemma_type: list_type,
9004 })),
9005 Some(source.clone()),
9006 ))
9007 }
9008
9009 fn imports_in_scope(&self, identity: &SpecIdentity) -> bool {
9011 self.data.values().any(|definition| {
9012 matches!(definition, DataDefinition::Import { row, .. } if *row.identity == *identity)
9013 })
9014 }
9015
9016 fn row_field_path(&mut self, build: &ListCommandBuild<'_>, name: &str) -> Option<DataPath> {
9019 let scope = build
9020 .row_scope
9021 .as_ref()
9022 .expect("BUG: field arguments are queued only with a row scope");
9023 let RowScope::Instance {
9024 template, identity, ..
9025 } = scope
9026 else {
9027 panic!("BUG: field arguments are queued only for instance rows")
9028 };
9029 let field = self
9030 .template_row_chain(identity, template, &[name])
9031 .and_then(|chain| match chain.as_slice() {
9032 [RowHop {
9033 end: RowEnd::Data(path),
9034 ..
9035 }] => Some(path.clone()),
9036 _ => None,
9037 });
9038 let Some(path) = field else {
9039 self.errors.push(self.engine_error(
9040 format!("row field `{name}` is not data on `{}`", identity.spec),
9041 &build.source,
9042 ));
9043 return None;
9044 };
9045 if build.fields.iter().any(|(existing, _)| *existing == path) {
9046 self.errors.push(self.engine_error(
9047 format!("row field `{name}` appears twice in one row"),
9048 &build.source,
9049 ));
9050 return None;
9051 }
9052 Some(path)
9053 }
9054
9055 fn finish_list_command(
9058 &mut self,
9059 command: &ast::ListCommand,
9060 row_scope: &Option<RowScope>,
9061 converted: Vec<ExprId>,
9062 fields: Vec<(DataPath, ExprId)>,
9063 failed: bool,
9064 source: &Source,
9065 ) -> Option<ListOp> {
9066 let mut converted = converted.into_iter();
9067 match command {
9068 ast::ListCommand::Find { .. } => Some(ListOp::Find {
9069 keys: converted.collect(),
9070 }),
9071 ast::ListCommand::First => Some(ListOp::First),
9072 ast::ListCommand::Last => Some(ListOp::Last),
9073 ast::ListCommand::Count => Some(ListOp::Count),
9074 ast::ListCommand::Sum { argument } => Some(ListOp::Reduce {
9075 kind: ListReduce::Sum,
9076 argument: optional_argument(argument, &mut converted),
9077 }),
9078 ast::ListCommand::Avg { argument } => Some(ListOp::Reduce {
9079 kind: ListReduce::Avg,
9080 argument: optional_argument(argument, &mut converted),
9081 }),
9082 ast::ListCommand::Product { argument } => Some(ListOp::Reduce {
9083 kind: ListReduce::Product,
9084 argument: optional_argument(argument, &mut converted),
9085 }),
9086 ast::ListCommand::Min { argument } => Some(ListOp::Reduce {
9087 kind: ListReduce::Min,
9088 argument: optional_argument(argument, &mut converted),
9089 }),
9090 ast::ListCommand::Max { argument } => Some(ListOp::Reduce {
9091 kind: ListReduce::Max,
9092 argument: optional_argument(argument, &mut converted),
9093 }),
9094 ast::ListCommand::Any { form } => {
9095 Some(list_predicate_op(form, ListPredKind::Any, &mut converted))
9096 }
9097 ast::ListCommand::All { form } => {
9098 Some(list_predicate_op(form, ListPredKind::All, &mut converted))
9099 }
9100 ast::ListCommand::Where { form } => {
9101 Some(list_predicate_op(form, ListPredKind::Where, &mut converted))
9102 }
9103 ast::ListCommand::Sort {
9104 descending,
9105 argument,
9106 } => Some(ListOp::Sort {
9107 descending: *descending,
9108 argument: optional_argument(argument, &mut converted),
9109 }),
9110 ast::ListCommand::Top { .. } => Some(ListOp::Top {
9111 n: next_argument(&mut converted),
9112 }),
9113 ast::ListCommand::With { rows } => match (rows, row_scope) {
9114 (ast::ListWithRows::Values(_), None) => Some(ListOp::With {
9115 rows: converted.collect(),
9116 }),
9117 (ast::ListWithRows::Values(_), Some(_)) => {
9118 (!failed).then_some(ListOp::With {
9120 rows: converted.collect(),
9121 })
9122 }
9123 (ast::ListWithRows::FieldPairs(_), None) => {
9124 if !failed {
9125 self.errors.push(self.engine_error(
9126 "`-> with field: value` appends a row to an instance list; this is a value list, write `-> with <value>`",
9127 source,
9128 ));
9129 }
9130 None
9131 }
9132 (ast::ListWithRows::FieldPairs(_), Some(scope)) => {
9133 let RowScope::Instance { identity, .. } = scope else {
9134 unreachable!(
9135 "BUG: `-> with field: value` converted under a value-list scope"
9136 )
9137 };
9138 (!failed).then_some(ListOp::WithRow {
9139 identity: identity.clone(),
9140 fields,
9141 })
9142 }
9143 },
9144 ast::ListCommand::Key { fields } => {
9145 let paths: Vec<DataPath> = fields
9146 .iter()
9147 .map(|field| DataPath {
9148 segments: Vec::new(),
9149 data: field.to_string(),
9150 })
9151 .collect();
9152 Some(ListOp::Key { fields: paths })
9153 }
9154 }
9155 }
9156}
9157
9158static EMPTY_INSTANCE_LIST: ast::InstanceListBody = ast::InstanceListBody {
9159 key: None,
9160 rows: Vec::new(),
9161 option_rows: Vec::new(),
9162};
9163
9164enum WrittenField {
9166 Value {
9168 key: Vec<String>,
9169 },
9170 NotData,
9171 Import {
9173 identity: Arc<SpecIdentity>,
9174 },
9175 List,
9176}
9177
9178struct ListSite<'s, 'a> {
9180 list_path: &'s DataPath,
9181 identity: &'s Arc<SpecIdentity>,
9183 template: &'s Arc<ExecutionPlan>,
9185 spec: &'a LemmaSpec,
9186 repository: &'s Arc<LemmaRepository>,
9188 effective: &'s EffectiveDate,
9189 bindings: &'s DataBindings,
9191}
9192
9193struct RowWrite {
9196 identity: Arc<SpecIdentity>,
9197 template: Arc<ExecutionPlan>,
9198 origin: RowOrigin,
9199 base: Vec<Option<RowBinding>>,
9200 bindings: DataBindings,
9201 prefix: Vec<PathSegment>,
9204 hops: u32,
9206}
9207
9208fn aliases_named(bindings: &DataBindings) -> Vec<String> {
9211 let mut names: Vec<String> = Vec::new();
9212 for (value, _) in bindings.values() {
9213 let BindingValue::InstanceAliases(aliases) = value else {
9214 continue;
9215 };
9216 for alias in aliases {
9217 if !names.contains(&alias.name) {
9218 names.push(alias.name.clone());
9219 }
9220 }
9221 }
9222 names
9223}
9224
9225fn alias_binding_source(bindings: &DataBindings, alias: &str) -> Option<Source> {
9227 bindings.values().find_map(|(value, source)| match value {
9228 BindingValue::InstanceAliases(aliases) if aliases.iter().any(|a| a.name == alias) => {
9229 Some(source.clone())
9230 }
9231 _ => None,
9232 })
9233}
9234
9235fn bindings_under(bindings: &DataBindings, alias: &str) -> DataBindings {
9238 bindings
9239 .iter()
9240 .filter(|(key, _)| key.len() > 1 && key[0] == alias)
9241 .map(|(key, value)| (key[1..].to_vec(), value.clone()))
9242 .collect()
9243}
9244
9245fn source_row_literal(
9247 value: &Value,
9248 field_type: &Arc<LemmaType>,
9249 template: &ExecutionPlan,
9250) -> Result<BoundValueKind, String> {
9251 source_row_literal_in(
9252 value,
9253 field_type,
9254 template.expression_unit_index(),
9255 &template.resolved_types.resolved,
9256 )
9257}
9258
9259fn source_row_literal_in(
9260 value: &Value,
9261 field_type: &Arc<LemmaType>,
9262 unit_index: &UnitIndex,
9263 named_types: &IndexMap<String, Arc<LemmaType>>,
9264) -> Result<BoundValueKind, String> {
9265 let typed = typed_literal_from_parsed(value, field_type, unit_index, named_types)?;
9266 execution_plan::validate_value_against_type(
9267 typed.lemma_type.as_ref(),
9268 &LiteralValue {
9269 value: typed.value.clone(),
9270 },
9271 unit_index,
9272 )?;
9273 Ok(BoundValueKind {
9274 value: typed.value,
9275 unit: typed.lemma_type.unit().map(Arc::from),
9276 })
9277}
9278
9279fn unit_tokens_for_spec<'b>(
9280 types: &'b ResolvedTypesMap,
9281 spec: &LemmaSpec,
9282) -> UnitTokenResolver<'b> {
9283 match find_types_by_spec(types, spec) {
9284 Some(resolved) => UnitTokenResolver::Declared {
9285 unit_index: &resolved.unit_index,
9286 named_types: &resolved.resolved,
9287 },
9288 None => UnitTokenResolver::Undeclared,
9289 }
9290}
9291
9292fn member_in_template(
9293 template: &ExecutionPlan,
9294 name: &str,
9295 error: impl Fn(String) -> Error,
9296) -> Result<ReferenceTarget, Error> {
9297 let data_path = DataPath::local(name.to_string());
9298 let rule_path = RulePath::new(Vec::new(), name.to_string());
9299 let is_data = template.data.contains_key(&data_path);
9300 let is_rule = template.rules.contains_key(&rule_path);
9301 if is_data && is_rule {
9302 return Err(error(format!(
9303 "Reference target '{name}' is ambiguous: both a data and a rule in spec '{}'",
9304 template.spec_name
9305 )));
9306 }
9307 if is_data {
9308 return Ok(ReferenceTarget::Data(data_path));
9309 }
9310 if is_rule {
9311 return Ok(ReferenceTarget::Rule(rule_path));
9312 }
9313 Err(error(format!(
9314 "Reference target '{name}' not found in spec '{}'",
9315 template.spec_name
9316 )))
9317}
9318
9319fn collect_unit_scale_targets(
9323 resolved_types: &ResolvedSpecTypes,
9324 data: &IndexMap<DataPath, DataDefinition>,
9325) -> IndexMap<(TypeFamily, String), ReferenceTarget> {
9326 let mut targets = IndexMap::new();
9327 for lemma_type in resolved_types.resolved.values() {
9328 let TypeSpecification::Measure { units, .. } = &lemma_type.specifications else {
9329 continue;
9330 };
9331 let Some(family) = lemma_type.measure_family().cloned() else {
9332 continue;
9333 };
9334 for unit in units.iter() {
9335 let Some(declared) = unit.resolved_scale_target() else {
9336 if unit.is_dynamic() {
9337 panic!(
9338 "BUG: dynamic unit '{}' on '{}' never went through scale resolution",
9339 unit.name,
9340 lemma_type.name()
9341 );
9342 }
9343 continue;
9344 };
9345 let import_path = resolved_types
9346 .unit_index
9347 .owners_for(&unit.name)
9348 .iter()
9349 .find(|owner| owner.lemma_type().same_measure_family(lemma_type.as_ref()))
9350 .unwrap_or_else(|| {
9351 panic!(
9352 "BUG: dynamic unit '{}' on '{}' has no same-family owner in the unit index",
9353 unit.name,
9354 lemma_type.name()
9355 )
9356 })
9357 .import_path();
9358 let consumer = prefix_scale_target(declared.clone(), import_path, data);
9359 targets.insert((family.clone(), unit.name.clone()), consumer);
9360 }
9361 }
9362 targets
9363}
9364
9365fn nodes_on_cycles<'k, K: Ord>(
9368 unsorted: &[&'k K],
9369 dependencies: impl Fn(&'k K) -> Vec<&'k K>,
9370) -> Vec<&'k K> {
9371 let remaining: BTreeSet<&K> = unsorted.iter().copied().collect();
9372 unsorted
9373 .iter()
9374 .copied()
9375 .filter(|&start| {
9376 let mut seen: BTreeSet<&K> = BTreeSet::new();
9377 let mut stack = dependencies(start);
9378 while let Some(node) = stack.pop() {
9379 if node == start {
9380 return true;
9381 }
9382 if remaining.contains(node) && seen.insert(node) {
9383 stack.extend(dependencies(node));
9384 }
9385 }
9386 false
9387 })
9388 .collect()
9389}
9390
9391fn prefix_scale_target(
9392 target: ReferenceTarget,
9393 import_path: &[String],
9394 data: &IndexMap<DataPath, DataDefinition>,
9395) -> ReferenceTarget {
9396 if import_path.is_empty() {
9397 return target;
9398 }
9399 let mut hops = Vec::new();
9400 for alias in import_path {
9401 let Some(DataDefinition::Import { row, .. }) = data.get(&DataPath::local(alias.clone()))
9402 else {
9403 panic!("BUG: unit owner import path '{alias}' is not a uses slot of this plan");
9404 };
9405 hops.push(PathSegment::import(alias, row.identity.as_ref()));
9406 }
9407 match target {
9408 ReferenceTarget::Data(path) => {
9409 hops.extend(path.segments);
9410 ReferenceTarget::Data(DataPath {
9411 segments: hops,
9412 data: path.data,
9413 })
9414 }
9415 ReferenceTarget::Rule(path) => {
9416 hops.extend(path.segments);
9417 ReferenceTarget::Rule(RulePath {
9418 segments: hops,
9419 rule: path.rule,
9420 })
9421 }
9422 }
9423}
9424
9425fn unscaled_units(value: &ValueKind, lemma_type: &LemmaType) -> Vec<(TypeFamily, String)> {
9428 let mut found = Vec::new();
9429 let mut pending: Vec<(&ValueKind, &LemmaType)> = vec![(value, lemma_type)];
9430 while let Some((value, lemma_type)) = pending.pop() {
9431 match value {
9432 ValueKind::UnscaledMeasure { unit, .. } => {
9433 let family = lemma_type.measure_family().unwrap_or_else(|| {
9434 panic!(
9435 "BUG: unscaled measure in unit '{unit}' typed '{}' without a measure family",
9436 lemma_type.name()
9437 )
9438 });
9439 found.push((family.clone(), unit.to_string()));
9440 }
9441 ValueKind::Range(left, right) => {
9442 pending.push((&right.value, right.lemma_type.as_ref()));
9443 pending.push((&left.value, left.lemma_type.as_ref()));
9444 }
9445 ValueKind::List(rows) => {
9446 if rows.stored().is_none() {
9447 for item in rows.values().iter().rev() {
9448 pending.push((&item.value, item.lemma_type.as_ref()));
9449 }
9450 }
9451 }
9452 _ => {}
9453 }
9454 }
9455 found
9456}
9457
9458fn find_types_by_spec<'b>(
9461 types: &'b ResolvedTypesMap,
9462 spec: &LemmaSpec,
9463) -> Option<&'b ResolvedSpecTypes> {
9464 types
9465 .iter()
9466 .find(|(_, s, _)| discovery::same_loaded_spec(s, spec))
9467 .map(|(_, _, t)| t)
9468}
9469
9470fn arithmetic_scope<'b>(resolved_types: &'b ResolvedTypesMap, graph: &Graph) -> MeasureScope<'b> {
9475 let main_spec = graph.main_spec();
9476 let types = find_types_by_spec(resolved_types, main_spec).unwrap_or_else(|| {
9477 panic!(
9478 "BUG: main spec '{}' typed before its types were resolved",
9479 main_spec.name
9480 )
9481 });
9482 MeasureScope {
9483 unit_index: &types.unit_index,
9484 signature_index: &types.signature_index,
9485 named_types: &types.resolved,
9486 }
9487}
9488
9489fn anonymous_rule_boundary_requires_rejection() -> bool {
9495 true
9496}
9497
9498fn anonymous_rule_boundary_error(
9504 rule_path: &RulePath,
9505 spec: &LemmaSpec,
9506 graph: &Graph,
9507 resolved_types: &ResolvedTypesMap,
9508 decomposition: &BaseMeasureVector,
9509 branch_index: Option<usize>,
9510) -> String {
9511 let candidates_hint = match find_unique_measure_type_in_unit_index(
9512 arithmetic_scope(resolved_types, graph).unit_index,
9513 decomposition,
9514 ) {
9515 DecompositionMatch::Multiple(family_names) => format!(
9516 " Multiple measure families in scope share these dimensions: {}. Give the rule an explicit named type.",
9517 family_names.join(", ")
9518 ),
9519 _ => String::new(),
9520 };
9521 match branch_index {
9522 Some(index) => format!(
9523 "Unless clause {} in rule '{}' (spec '{}') returns an anonymous intermediate with unresolved dimensions {:?}. Give the rule a named measure or ratio type with declared units, or rewrite the expression so dimensions resolve to a named type in scope.{}",
9524 index, rule_path.rule, spec.name, decomposition, candidates_hint
9525 ),
9526 None => format!(
9527 "Rule '{}' in spec '{}' returns an anonymous intermediate with unresolved dimensions {:?}. Give the rule a named measure or ratio type with declared units, or rewrite the expression so dimensions resolve to a named type in scope.{}",
9528 rule_path.rule, spec.name, decomposition, candidates_hint
9529 ),
9530 }
9531}
9532
9533fn inferred_or_dropped(
9541 graph: &Graph,
9542 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
9543 rule_path: &RulePath,
9544) -> Arc<LemmaType> {
9545 if let Some(lemma_type) = computed_rule_types.get(rule_path) {
9546 return Arc::clone(lemma_type);
9547 }
9548 match graph.rule_presence(rule_path) {
9549 RulePresence::Dropped => Arc::new(LemmaType::undetermined_type()),
9550 RulePresence::Present => {
9551 panic!("BUG: rule type not inferred before dependent in topological order")
9552 }
9553 RulePresence::Absent => {
9554 panic!("BUG: rule '{rule_path}' neither in graph nor dropped")
9555 }
9556 }
9557}
9558
9559fn infer_expression_type(
9563 expression: ExprRef<'_, ExpressionKind>,
9564 graph: &Graph,
9565 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
9566 resolved_types: &ResolvedTypesMap,
9567 cache: &mut InferExpressionTypeCache,
9568) -> Arc<LemmaType> {
9569 let key = expression.table as *const ExprTable<ExpressionKind> as usize;
9570 let types = cache
9571 .tables
9572 .entry(key)
9573 .or_insert_with(|| Vec::with_capacity(expression.table.len()));
9574 if types.is_empty() {
9575 cache.typed.push(key);
9576 }
9577 for index in types.len()..=expression.id.index() {
9578 let node = expression.child(ExprId::from_index(index));
9579 let lemma_type =
9580 infer_expression_type_uncached(node, graph, computed_rule_types, resolved_types, types);
9581 types.push(lemma_type);
9582 }
9583 Arc::clone(&types[expression.id.index()])
9584}
9585
9586fn cached_expression_type(
9589 cache: &[Arc<LemmaType>],
9590 expression: ExprRef<'_, ExpressionKind>,
9591) -> Arc<LemmaType> {
9592 Arc::clone(
9593 cache
9594 .get(expression.id.index())
9595 .expect("BUG: operand typed before its parent"),
9596 )
9597}
9598
9599fn infer_expression_type_uncached(
9600 expression: ExprRef<'_, ExpressionKind>,
9601 graph: &Graph,
9602 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
9603 resolved_types: &ResolvedTypesMap,
9604 cache: &[Arc<LemmaType>],
9605) -> Arc<LemmaType> {
9606 match expression.kind() {
9607 ExpressionKind::Literal(literal_value) => Arc::clone(&literal_value.lemma_type),
9608
9609 ExpressionKind::DataPath(data_path) => {
9610 infer_data_type(data_path, graph, computed_rule_types)
9611 }
9612
9613 ExpressionKind::RulePath(rule_path) => {
9614 inferred_or_dropped(graph, computed_rule_types, rule_path)
9615 }
9616
9617 ExpressionKind::Logical { first, steps, .. } => steps.iter().fold(
9618 cached_expression_type(cache, expression.child(*first)),
9619 |left_type, step| {
9620 let right_type = cached_expression_type(cache, expression.child(step.operand));
9621 logical_connective_type(&left_type, &right_type)
9622 },
9623 ),
9624
9625 ExpressionKind::LogicalNegation(operand, _) => {
9626 let operand_type = cached_expression_type(cache, expression.child(*operand));
9627 logical_not_type(&operand_type)
9628 }
9629
9630 ExpressionKind::Comparison(left, _, right) => {
9631 let left_type = cached_expression_type(cache, expression.child(*left));
9632 let right_type = cached_expression_type(cache, expression.child(*right));
9633 comparison_type(&left_type, &right_type)
9634 }
9635
9636 ExpressionKind::Arithmetic { first, steps } => {
9637 let scope = arithmetic_scope(resolved_types, graph);
9638 steps.iter().fold(
9639 cached_expression_type(cache, expression.child(*first)),
9640 |left_type, step| {
9641 let right = expression.child(step.operand);
9642 let right_type = cached_expression_type(cache, right);
9643 arithmetic_step_type(left_type, &step.op, right, right_type, &scope)
9644 },
9645 )
9646 }
9647
9648 ExpressionKind::UnitConversion(source_expression, target) => {
9649 let source_type = cached_expression_type(cache, expression.child(*source_expression));
9650 unit_conversion_type(&source_type, target)
9651 }
9652
9653 ExpressionKind::MathematicalComputation(op, operand) => {
9654 let operand_type = cached_expression_type(cache, expression.child(*operand));
9655 math_op_type(op, &operand_type, &arithmetic_scope(resolved_types, graph))
9656 }
9657
9658 ExpressionKind::StepRounding { operand, step, .. } => step_rounding_type(
9659 &cached_expression_type(cache, expression.child(*operand)),
9660 step.map(|step| cached_expression_type(cache, expression.child(step)))
9661 .as_ref(),
9662 ),
9663
9664 ExpressionKind::RangeBound(_, operand) => {
9665 let operand_type = cached_expression_type(cache, expression.child(*operand));
9666 if operand_type.vetoed() {
9667 return Arc::new(LemmaType::veto_type());
9668 }
9669 if operand_type.is_undetermined() {
9670 return Arc::new(LemmaType::undetermined_type());
9671 }
9672 range_bound_result_type(
9673 operand_type.as_ref(),
9674 &arithmetic_scope(resolved_types, graph),
9675 )
9676 }
9677
9678 ExpressionKind::Veto(_) => Arc::new(LemmaType::veto_type()),
9679
9680 ExpressionKind::ResultIsVeto(operand) => {
9681 cached_expression_type(cache, expression.child(*operand));
9682 result_is_veto_type()
9683 }
9684
9685 ExpressionKind::Now => primitive_date_arc().clone(),
9686
9687 ExpressionKind::DateRelative(_, date_expr)
9688 | ExpressionKind::DateCalendar(_, _, date_expr) => {
9689 let date_type = cached_expression_type(cache, expression.child(*date_expr));
9690 date_predicate_type(&date_type)
9691 }
9692
9693 ExpressionKind::RangeContainment(value, range) => {
9694 let value_type = cached_expression_type(cache, expression.child(*value));
9695 let range_type = cached_expression_type(cache, expression.child(*range));
9696 if value_type.vetoed() || range_type.vetoed() {
9697 return Arc::new(LemmaType::veto_type());
9698 }
9699 primitive_boolean_arc().clone()
9700 }
9701
9702 ExpressionKind::RangeLiteral(left, right) => {
9703 let left_type = cached_expression_type(cache, expression.child(*left));
9704 let right_type = cached_expression_type(cache, expression.child(*right));
9705 if left_type.vetoed() || right_type.vetoed() {
9706 return Arc::new(LemmaType::veto_type());
9707 }
9708 if left_type.is_undetermined() || right_type.is_undetermined() {
9709 return Arc::new(LemmaType::undetermined_type());
9710 }
9711 infer_range_type_from_endpoint_types(left_type.as_ref(), right_type.as_ref())
9712 }
9713
9714 ExpressionKind::PastFutureRange(_, offset_expr) => {
9715 let offset_type = cached_expression_type(cache, expression.child(*offset_expr));
9716 past_future_range_type(&offset_type)
9717 }
9718
9719 ExpressionKind::SpecMember { base, path, name } => {
9720 let base_type = cached_expression_type(cache, expression.child(*base));
9721 if base_type.vetoed() {
9722 return Arc::new(LemmaType::veto_type());
9723 }
9724 if !base_type.is_spec() {
9725 return Arc::new(LemmaType::undetermined_type());
9726 }
9727 match row_member_end(graph, &base_type, path, name) {
9728 Some((fields, end)) => fields.end_type(&end, computed_rule_types),
9729 None => Arc::new(LemmaType::undetermined_type()),
9730 }
9731 }
9732
9733 ExpressionKind::RowField { identity, end, .. } => row_fields(graph, identity)
9734 .map(|fields| fields.end_type(end, computed_rule_types))
9735 .unwrap_or_else(|| Arc::new(LemmaType::undetermined_type())),
9736 ExpressionKind::RowValue { list, .. } => {
9737 let list_type = cached_expression_type(cache, expression.child(*list));
9738 list_element_type(&list_type, graph, resolved_types)
9739 }
9740
9741 ExpressionKind::Piecewise(arms) => {
9742 let mut result_type: Option<Arc<LemmaType>> = None;
9743 for (condition, result) in arms {
9744 let condition_type = cached_expression_type(cache, expression.child(*condition));
9745 if !condition_type.is_boolean() && !condition_type.is_undetermined() {
9746 return Arc::new(LemmaType::undetermined_type());
9747 }
9748 let arm_result_type = cached_expression_type(cache, expression.child(*result));
9749 match &result_type {
9750 None => result_type = Some(arm_result_type),
9751 Some(existing) if *existing == arm_result_type => {}
9752 Some(_)
9753 if arm_result_type.is_undetermined()
9754 || result_type.as_ref().is_some_and(|t| t.is_undetermined()) =>
9755 {
9756 result_type = Some(Arc::new(LemmaType::undetermined_type()));
9757 }
9758 Some(_) => return Arc::new(LemmaType::undetermined_type()),
9759 }
9760 }
9761 result_type.unwrap_or_else(|| Arc::new(LemmaType::undetermined_type()))
9762 }
9763
9764 ExpressionKind::ListCommand { list, op, .. } => infer_list_command_type(
9765 expression,
9766 expression.child(*list),
9767 op,
9768 graph,
9769 resolved_types,
9770 cache,
9771 ),
9772 }
9773}
9774
9775#[derive(Clone, Copy)]
9777enum RowFields<'g> {
9778 Plan(&'g ExecutionPlan),
9780 Root(&'g Graph<'g>),
9782}
9783
9784impl<'g> RowFields<'g> {
9785 fn chain(
9787 &self,
9788 graph: &Graph<'_>,
9789 identity: &SpecIdentity,
9790 parts: &[&str],
9791 ) -> Option<Vec<RowHop>> {
9792 let templates = &graph.instance_lists().templates;
9793 match self {
9794 RowFields::Plan(plan) => instance_list::row_end_chain(
9795 identity,
9796 instance_list::RowEndView::Plan(plan),
9797 templates,
9798 parts,
9799 ),
9800 RowFields::Root(root) => {
9801 let rules: BTreeSet<RulePath> = root.rules().keys().cloned().collect();
9804 instance_list::row_end_chain(
9805 identity,
9806 instance_list::RowEndView::Root {
9807 data: root.data(),
9808 rules: &rules,
9809 },
9810 templates,
9811 parts,
9812 )
9813 }
9814 }
9815 }
9816
9817 fn end_type(
9820 &self,
9821 end: &RowEnd,
9822 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
9823 ) -> Arc<LemmaType> {
9824 match (self, end) {
9825 (RowFields::Plan(plan), _) => instance_list::row_end_type(plan, end),
9826 (RowFields::Root(graph), RowEnd::Data(path)) => {
9827 if let Some(DataDefinition::Reference {
9828 target: ReferenceTarget::Rule(rule),
9829 ..
9830 }) = graph.data().get(path)
9831 {
9832 return inferred_or_dropped(graph, computed_rule_types, rule);
9833 }
9834 instance_list::row_data_type(graph.data(), path)
9835 }
9836 (RowFields::Root(graph), RowEnd::Rule(rule)) => {
9837 inferred_or_dropped(graph, computed_rule_types, rule)
9838 }
9839 }
9840 }
9841
9842 fn default(&self, path: &DataPath) -> Option<TypedLiteral> {
9843 match self {
9844 RowFields::Plan(plan) => instance_list::default_of(&plan.data, path),
9845 RowFields::Root(graph) => instance_list::default_of(graph.data(), path),
9846 }
9847 }
9848}
9849
9850fn row_fields<'g>(graph: &'g Graph<'g>, identity: &SpecIdentity) -> Option<RowFields<'g>> {
9853 if *identity == graph.instance_lists().self_identity {
9854 return Some(RowFields::Root(graph));
9855 }
9856 graph
9857 .instance_lists()
9858 .templates
9859 .get(identity)
9860 .map(|plan| RowFields::Plan(plan.as_ref()))
9861}
9862
9863fn row_member_end<'g>(
9866 graph: &'g Graph<'g>,
9867 base_type: &LemmaType,
9868 path: &[String],
9869 name: &str,
9870) -> Option<(RowFields<'g>, RowEnd)> {
9871 let TypeSpecification::Spec { spec: identity } = &base_type.specifications else {
9872 return None;
9873 };
9874 let is_row = *identity == graph.instance_lists().self_identity
9875 || graph.instance_lists().templates.contains_key(identity);
9876 if !is_row {
9877 return None;
9878 }
9879 let fields = row_fields(graph, identity)
9880 .expect("BUG: row member identity already checked for a template");
9881 let mut parts: Vec<&str> = path.iter().map(String::as_str).collect();
9882 parts.push(name);
9883 let last = fields
9884 .chain(graph, identity, &parts)?
9885 .pop()
9886 .expect("BUG: a row end chain has a last hop");
9887 let fields = row_fields(graph, &last.identity)
9888 .expect("BUG: every hop of a row end chain has template fields");
9889 Some((fields, last.end))
9890}
9891
9892fn infer_list_command_type(
9893 node: ExprRef<'_, ExpressionKind>,
9894 list: ExprRef<'_, ExpressionKind>,
9895 op: &ListOp,
9896 graph: &Graph,
9897 resolved_types: &ResolvedTypesMap,
9898 cache: &[Arc<LemmaType>],
9899) -> Arc<LemmaType> {
9900 let list_type = cached_expression_type(cache, list);
9901 if list_type.vetoed() {
9902 return Arc::new(LemmaType::veto_type());
9903 }
9904 if list_type.is_undetermined() {
9905 return Arc::new(LemmaType::undetermined_type());
9906 }
9907 if !list_type.is_list() {
9908 return Arc::new(LemmaType::undetermined_type());
9909 }
9910 let instance_rows = list_has_instance_rows(&list_type);
9911 let kind = match op {
9912 ListOp::Count => ListCommandResultKind::Count,
9913 ListOp::First | ListOp::Last | ListOp::Find { .. } => ListCommandResultKind::Element,
9914 ListOp::Reduce {
9915 argument: Some(_), ..
9916 } if instance_rows => ListCommandResultKind::InstanceReduce,
9917 ListOp::Reduce { argument: None, .. } if !instance_rows => ListCommandResultKind::Element,
9918 ListOp::Reduce { .. } => return Arc::new(LemmaType::undetermined_type()),
9919 ListOp::Sort {
9920 argument: Some(_), ..
9921 } if !instance_rows => return Arc::new(LemmaType::undetermined_type()),
9922 ListOp::Predicate { kind, .. } => match kind {
9923 ListPredKind::Any | ListPredKind::All => ListCommandResultKind::BooleanPredicate,
9924 ListPredKind::Where => ListCommandResultKind::KeepList,
9925 },
9926 ListOp::Sort { .. }
9927 | ListOp::Top { .. }
9928 | ListOp::With { .. }
9929 | ListOp::WithRow { .. }
9930 | ListOp::Key { .. } => ListCommandResultKind::KeepList,
9931 };
9932 let first_argument = match op {
9933 ListOp::Reduce {
9934 argument: Some(argument),
9935 ..
9936 } => Some(cached_expression_type(cache, node.child(*argument))),
9937 _ => None,
9938 };
9939 let result = list_command_result_type(
9940 &list_type,
9941 kind,
9942 first_argument.as_ref(),
9943 &arithmetic_scope(resolved_types, graph),
9944 );
9945 if let ListOp::Key { fields } = op {
9946 refine_list_key_type(&result, fields)
9947 } else {
9948 result
9949 }
9950}
9951
9952fn list_element_type(
9953 list_type: &LemmaType,
9954 graph: &Graph,
9955 resolved_types: &ResolvedTypesMap,
9956) -> Arc<LemmaType> {
9957 resolve_anonymous_measure(
9958 list_type.list_element_lemma_type(),
9959 &arithmetic_scope(resolved_types, graph),
9960 )
9961}
9962
9963fn infer_data_type(
9973 data_path: &DataPath,
9974 graph: &Graph,
9975 computed_rule_types: &HashMap<RulePath, Arc<LemmaType>>,
9976) -> Arc<LemmaType> {
9977 let Some(definition) = graph.data().get(data_path) else {
9978 return Arc::new(LemmaType::undetermined_type());
9979 };
9980 match definition {
9981 DataDefinition::Value { resolved_type, .. } => Arc::clone(resolved_type),
9982 DataDefinition::TypeDeclaration { resolved_type, .. } => Arc::clone(resolved_type),
9983 DataDefinition::Reference {
9984 target: ReferenceTarget::Rule(target_rule),
9985 resolved_type,
9986 ..
9987 } => {
9988 if !resolved_type.is_undetermined() {
9989 Arc::clone(resolved_type)
9990 } else {
9991 inferred_or_dropped(graph, computed_rule_types, target_rule)
9992 }
9993 }
9994 DataDefinition::Reference { resolved_type, .. }
9995 | DataDefinition::InstanceList { resolved_type, .. } => Arc::clone(resolved_type),
9996 DataDefinition::Import { row, .. } => {
9997 Arc::new(LemmaType::primitive(TypeSpecification::Spec {
9998 spec: row.identity.as_ref().clone(),
9999 }))
10000 }
10001 }
10002}
10003
10004fn engine_error_at_graph(graph: &Graph, source: &Source, message: impl Into<String>) -> Error {
10009 Error::validation_with_context(
10010 message.into(),
10011 Some(source.clone()),
10012 None::<String>,
10013 Some(graph.main_spec),
10014 None,
10015 )
10016}
10017
10018fn check_logical_operands(
10019 graph: &Graph,
10020 connective: ast::LogicalConnective,
10021 left_type: &LemmaType,
10022 right_type: &LemmaType,
10023 source: &Source,
10024) -> Result<(), Vec<Error>> {
10025 if left_type.vetoed() || right_type.vetoed() {
10026 return Ok(());
10027 }
10028 if !left_type.is_boolean() {
10029 return Err(vec![engine_error_at_graph(
10030 graph,
10031 source,
10032 format!(
10033 "Logical {} requires boolean left operand, got {}",
10034 connective.keyword().to_ascii_uppercase(),
10035 left_type
10036 ),
10037 )]);
10038 }
10039 if !right_type.is_boolean() {
10040 return Err(vec![engine_error_at_graph(
10041 graph,
10042 source,
10043 format!(
10044 "Logical {} requires boolean right operand, got {}",
10045 connective.keyword().to_ascii_uppercase(),
10046 right_type
10047 ),
10048 )]);
10049 }
10050 Ok(())
10051}
10052
10053fn check_logical_operand(
10054 graph: &Graph,
10055 operand_type: &LemmaType,
10056 source: &Source,
10057) -> Result<(), Vec<Error>> {
10058 if operand_type.vetoed() {
10059 return Ok(());
10060 }
10061 if !operand_type.is_boolean() {
10062 Err(vec![engine_error_at_graph(
10063 graph,
10064 source,
10065 format!(
10066 "Logical negation requires boolean operand, got {}",
10067 operand_type
10068 ),
10069 )])
10070 } else {
10071 Ok(())
10072 }
10073}
10074
10075fn spec_value_not_operable(
10076 graph: &Graph,
10077 left_type: &LemmaType,
10078 right_type: &LemmaType,
10079 source: &Source,
10080) -> Option<Error> {
10081 let spec_type = if left_type.is_spec() {
10082 Some(left_type)
10083 } else if right_type.is_spec() {
10084 Some(right_type)
10085 } else {
10086 None
10087 };
10088 spec_type.map(|spec_type| {
10089 engine_error_at_graph(
10090 graph,
10091 source,
10092 format!(
10093 "spec instance is not a value here (got {}); read a field or use `is veto`",
10094 spec_type.name()
10095 ),
10096 )
10097 })
10098}
10099
10100fn check_range_containment_types(
10101 graph: &Graph,
10102 value_type: &LemmaType,
10103 range_type: &LemmaType,
10104 source: &Source,
10105) -> Result<(), Vec<Error>> {
10106 if value_type.vetoed() || range_type.vetoed() {
10107 return Ok(());
10108 }
10109 if !range_type.is_range() {
10110 return Err(vec![engine_error_at_graph(
10111 graph,
10112 source,
10113 format!(
10114 "Right side of 'in' must be a range, got type '{}'",
10115 range_type.name()
10116 ),
10117 )]);
10118 }
10119 let compatible = (range_type.is_date_range() && value_type.is_date())
10120 || (range_type.is_time_range() && (value_type.is_time() || value_type.is_date()))
10121 || (range_type.is_number_range() && value_type.is_number())
10122 || (range_type.is_measure_range()
10123 && value_type.is_measure()
10124 && measure_range_matches_measure(range_type, value_type))
10125 || (range_type.is_ratio_range() && value_type.is_ratio())
10126 || (range_type.is_calendar_like_range() && value_type.is_calendar_like());
10127 if !compatible {
10128 return Err(vec![engine_error_at_graph(
10129 graph,
10130 source,
10131 format!(
10132 "Cannot test whether {} is in {}.",
10133 value_type.name(),
10134 range_type.name()
10135 ),
10136 )]);
10137 }
10138 Ok(())
10139}
10140
10141fn check_comparison_types(
10142 graph: &Graph,
10143 left_type: &LemmaType,
10144 op: &ComparisonComputation,
10145 right_type: &LemmaType,
10146 source: &Source,
10147) -> Result<(), Vec<Error>> {
10148 if left_type.vetoed() || right_type.vetoed() {
10149 return Ok(());
10150 }
10151 if let Some(error) = spec_value_not_operable(graph, left_type, right_type, source) {
10152 return Err(vec![error]);
10153 }
10154 let is_equality_only = matches!(op, ComparisonComputation::Is | ComparisonComputation::IsNot);
10155
10156 if left_type.is_range() {
10157 if range_matches_measure_type(left_type, right_type) {
10158 return Ok(());
10159 }
10160 return Err(vec![engine_error_at_graph(
10161 graph,
10162 source,
10163 format!("Cannot compare {} with {}", left_type, right_type),
10164 )]);
10165 }
10166
10167 if left_type.is_boolean() && right_type.is_boolean() {
10168 if !is_equality_only {
10169 return Err(vec![engine_error_at_graph(
10170 graph,
10171 source,
10172 format!("Can only use 'is' and 'is not' with booleans (got {})", op),
10173 )]);
10174 }
10175 return Ok(());
10176 }
10177
10178 if left_type.is_text() && right_type.is_text() {
10179 if !is_equality_only {
10180 return Err(vec![engine_error_at_graph(
10181 graph,
10182 source,
10183 format!("Can only use 'is' and 'is not' with text (got {})", op),
10184 )]);
10185 }
10186 return Ok(());
10187 }
10188
10189 if left_type.is_number() && right_type.is_number() {
10190 return Ok(());
10191 }
10192
10193 if left_type.is_ratio() && right_type.is_ratio() {
10194 return Ok(());
10195 }
10196
10197 if left_type.is_date() && right_type.is_date() {
10198 return Ok(());
10199 }
10200
10201 if left_type.is_time() && right_type.is_time() {
10202 return Ok(());
10203 }
10204
10205 if left_type.is_measure() && right_type.is_measure() {
10206 if left_type.is_anonymous_measure() || right_type.is_anonymous_measure() {
10207 return Err(vec![engine_error_at_graph(
10208 graph,
10209 source,
10210 format!(
10211 "Cannot compare anonymous measures {} and {}.",
10212 left_type.name(),
10213 right_type.name()
10214 ),
10215 )]);
10216 }
10217 let same_decomposition = match (
10218 left_type.measure_type_decomposition(),
10219 right_type.measure_type_decomposition(),
10220 ) {
10221 (Some(ld), Some(rd)) => ld == rd,
10222 _ => false,
10223 };
10224 if !left_type.same_measure_family(right_type)
10225 && !left_type.compatible_with_anonymous_measure(right_type)
10226 && !same_decomposition
10227 {
10228 return Err(vec![engine_error_at_graph(
10229 graph,
10230 source,
10231 format!(
10232 "Cannot compare unrelated measure types: {} and {}",
10233 left_type.name(),
10234 right_type.name()
10235 ),
10236 )]);
10237 }
10238 return Ok(());
10239 }
10240
10241 if left_type.is_duration_like() && right_type.is_duration_like() {
10242 return Ok(());
10243 }
10244 if left_type.is_calendar_like() && right_type.is_calendar_like() {
10245 return Ok(());
10246 }
10247 if left_type.is_calendar_like() && right_type.is_number() {
10248 return Ok(());
10249 }
10250 if left_type.is_number() && right_type.is_calendar_like() {
10251 return Ok(());
10252 }
10253
10254 Err(vec![engine_error_at_graph(
10255 graph,
10256 source,
10257 format!("Cannot compare {} with {}", left_type, right_type),
10258 )])
10259}
10260
10261fn arithmetic_literal_zero_divisor_planning_errors(
10263 graph: &Graph,
10264 right: ExprRef<'_, ExpressionKind>,
10265 operator: &ArithmeticComputation,
10266 source: &Source,
10267) -> Result<(), Vec<Error>> {
10268 if !matches!(
10269 operator,
10270 ArithmeticComputation::Divide | ArithmeticComputation::Modulo
10271 ) {
10272 return Ok(());
10273 }
10274
10275 if let ExpressionKind::Literal(literal) = right.kind() {
10276 if let ValueKind::Number(number) = &literal.value {
10277 if rational_is_zero(number) {
10278 return Err(vec![engine_error_at_graph(
10279 graph,
10280 source,
10281 format!("Cannot apply '{}' with a zero divisor literal.", operator),
10282 )]);
10283 }
10284 }
10285 }
10286
10287 Ok(())
10288}
10289
10290enum IntegerExponent {
10291 NotLiteral,
10292 Value(i32),
10293 OutOfRange,
10294}
10295
10296fn integer_exponent_literal(expr: ExprRef<'_, ExpressionKind>) -> IntegerExponent {
10297 let ExpressionKind::Literal(literal) = expr.kind() else {
10298 return IntegerExponent::NotLiteral;
10299 };
10300 let ValueKind::Number(number) = &literal.value else {
10301 return IntegerExponent::NotLiteral;
10302 };
10303 if !number.is_integer() {
10304 return IntegerExponent::NotLiteral;
10305 }
10306 match number.numer_to_i32() {
10307 Some(exponent) => IntegerExponent::Value(exponent),
10308 None => IntegerExponent::OutOfRange,
10309 }
10310}
10311
10312fn arithmetic_step_type(
10315 left_type: Arc<LemmaType>,
10316 op: &ArithmeticComputation,
10317 right: ExprRef<'_, ExpressionKind>,
10318 right_type: Arc<LemmaType>,
10319 scope: &MeasureScope<'_>,
10320) -> Arc<LemmaType> {
10321 if *op == ArithmeticComputation::Power && left_type.is_measure() {
10322 match integer_exponent_literal(right) {
10323 IntegerExponent::Value(exponent) => {
10324 if measure_power_scale(left_type.as_ref(), exponent).is_none() {
10325 return Arc::new(LemmaType::undetermined_type());
10326 }
10327 return quantity_integer_power_type(&left_type, exponent, scope);
10328 }
10329 IntegerExponent::OutOfRange => return Arc::new(LemmaType::undetermined_type()),
10330 IntegerExponent::NotLiteral => {}
10331 }
10332 }
10333 compute_arithmetic_result_type(left_type, op, right_type, scope)
10334}
10335
10336fn measure_power_scale(left: &LemmaType, exponent: i32) -> Option<BaseMeasureVector> {
10337 let decomposition = left
10338 .measure_type_decomposition()
10339 .expect("BUG: measure power requires a resolved decomposition");
10340 scale_decomposition(decomposition, exponent)
10341}
10342
10343fn measure_power_cap_error(
10344 graph: &Graph,
10345 left_type: &LemmaType,
10346 op: &ArithmeticComputation,
10347 right: ExprRef<'_, ExpressionKind>,
10348 source: &Source,
10349) -> Option<Error> {
10350 if *op != ArithmeticComputation::Power || !left_type.is_measure() {
10351 return None;
10352 }
10353 let overflow = match integer_exponent_literal(right) {
10354 IntegerExponent::OutOfRange => true,
10355 IntegerExponent::Value(exponent) => measure_power_scale(left_type, exponent).is_none(),
10356 IntegerExponent::NotLiteral => false,
10357 };
10358 if !overflow {
10359 return None;
10360 }
10361 Some(engine_error_at_graph(
10362 graph,
10363 source,
10364 format!(
10365 "Cannot apply '^' to {} and number: a resulting unit exponent exceeds the maximum of {}.",
10366 left_type.name(),
10367 MAX_UNIT_EXPONENT
10368 ),
10369 ))
10370}
10371
10372fn arithmetic_power_exponent_planning_errors(
10373 graph: &Graph,
10374 right: ExprRef<'_, ExpressionKind>,
10375 left_type: &LemmaType,
10376 operator: &ArithmeticComputation,
10377 source: &Source,
10378) -> Result<(), Vec<Error>> {
10379 if *operator != ArithmeticComputation::Power {
10380 return Ok(());
10381 }
10382 if left_type.is_measure() || left_type.is_duration_like() {
10385 let is_integer_literal = if let ExpressionKind::Literal(lit) = right.kind() {
10386 if let crate::planning::semantics::ValueKind::Number(n) = &lit.value {
10387 n.is_integer()
10388 } else {
10389 false
10390 }
10391 } else {
10392 false
10393 };
10394 if !is_integer_literal {
10395 return Err(vec![engine_error_at_graph(
10396 graph,
10397 source,
10398 "Cannot raise a measure value to a fractional or variable exponent. Use a positive integer literal.".to_string(),
10399 )]);
10400 }
10401 }
10402 Ok(())
10403}
10404
10405fn arithmetic_plan_time_exactness_planning_errors(
10408 graph: &Graph,
10409 right: ExprRef<'_, ExpressionKind>,
10410 left_type: &LemmaType,
10411 right_type: &LemmaType,
10412 operator: &ArithmeticComputation,
10413 source: &Source,
10414) -> Result<(), Vec<Error>> {
10415 if left_type.vetoed() || right_type.vetoed() {
10416 return Ok(());
10417 }
10418 if left_type.is_undetermined() || right_type.is_undetermined() {
10419 return Ok(());
10420 }
10421
10422 let mut errors = Vec::new();
10423 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
10424 if let Err(mut errs) = result {
10425 errors.append(&mut errs);
10426 }
10427 };
10428
10429 collect(
10430 arithmetic_literal_zero_divisor_planning_errors(graph, right, operator, source),
10431 &mut errors,
10432 );
10433 collect(
10434 arithmetic_power_exponent_planning_errors(graph, right, left_type, operator, source),
10435 &mut errors,
10436 );
10437
10438 if errors.is_empty() {
10439 Ok(())
10440 } else {
10441 Err(errors)
10442 }
10443}
10444
10445fn operand_forbids_modulo(ty: &LemmaType) -> bool {
10446 ty.is_ratio() || ty.is_measure() || ty.is_duration_like() || ty.is_calendar_like()
10447}
10448
10449fn check_arithmetic_types(
10450 graph: &Graph,
10451 left_type: &LemmaType,
10452 right_type: &LemmaType,
10453 operator: &ArithmeticComputation,
10454 source: &Source,
10455) -> Result<(), Vec<Error>> {
10456 if left_type.vetoed() || right_type.vetoed() {
10457 return Ok(());
10458 }
10459 if let Some(error) = spec_value_not_operable(graph, left_type, right_type, source) {
10460 return Err(vec![error]);
10461 }
10462
10463 if left_type.is_range() || right_type.is_range() {
10464 let range_measure_allowed = matches!(
10465 operator,
10466 ArithmeticComputation::Add | ArithmeticComputation::Subtract
10467 ) && ((left_type.is_range()
10468 && right_type.is_range()
10469 && range_matches_range_measure(left_type, right_type))
10470 || (left_type.is_range()
10471 && !right_type.is_range()
10472 && range_matches_measure_type(left_type, right_type))
10473 || (right_type.is_range()
10474 && !left_type.is_range()
10475 && range_matches_measure_type(right_type, left_type)));
10476 if range_measure_allowed {
10477 return Ok(());
10478 }
10479
10480 return Err(vec![engine_error_at_graph(
10481 graph,
10482 source,
10483 format!(
10484 "Cannot apply '{}' to {} and {}.",
10485 operator,
10486 left_type.name(),
10487 right_type.name()
10488 ),
10489 )]);
10490 }
10491
10492 if left_type.is_date() || left_type.is_time() || right_type.is_date() || right_type.is_time() {
10494 if matches!(operator, ArithmeticComputation::Subtract) {
10495 if left_type.is_date() && right_type.is_date() {
10496 return Err(vec![engine_error_at_graph(
10497 graph,
10498 source,
10499 "Cannot subtract dates. Use a date range instead: `start...end as day` or `start...end as year`.".to_string(),
10500 )]);
10501 }
10502 if left_type.is_time() && right_type.is_time() {
10503 return Err(vec![engine_error_at_graph(
10504 graph,
10505 source,
10506 "Cannot subtract times. Use a datetime range instead: `start...end as hour` or `start...end as second`.".to_string(),
10507 )]);
10508 }
10509 }
10510
10511 let left_is_duration_like = left_type.is_duration_like();
10512 let right_is_duration_like = right_type.is_duration_like();
10513 let valid = matches!(
10514 (
10515 left_type.is_date(),
10516 left_type.is_time(),
10517 right_type.is_date(),
10518 right_type.is_time(),
10519 left_is_duration_like,
10520 right_is_duration_like,
10521 left_type.is_calendar_like(),
10522 right_type.is_calendar_like(),
10523 operator
10524 ),
10525 (
10526 true,
10527 _,
10528 _,
10529 true,
10530 _,
10531 _,
10532 _,
10533 _,
10534 ArithmeticComputation::Subtract
10535 ) | (
10536 true,
10537 _,
10538 _,
10539 _,
10540 _,
10541 true,
10542 _,
10543 _,
10544 ArithmeticComputation::Add | ArithmeticComputation::Subtract
10545 ) | (
10546 true,
10547 _,
10548 _,
10549 _,
10550 _,
10551 _,
10552 _,
10553 true,
10554 ArithmeticComputation::Add | ArithmeticComputation::Subtract
10555 ) | (_, _, true, _, true, _, _, _, ArithmeticComputation::Add)
10556 | (_, _, true, _, _, _, true, _, ArithmeticComputation::Add)
10557 | (
10558 _,
10559 true,
10560 _,
10561 _,
10562 _,
10563 true,
10564 _,
10565 _,
10566 ArithmeticComputation::Add | ArithmeticComputation::Subtract
10567 )
10568 | (_, _, _, true, true, _, _, _, ArithmeticComputation::Add)
10569 );
10570 if !valid {
10571 return Err(vec![engine_error_at_graph(
10572 graph,
10573 source,
10574 format!(
10575 "Cannot apply '{}' to {} and {}.",
10576 operator,
10577 left_type.name(),
10578 right_type.name()
10579 ),
10580 )]);
10581 }
10582 return Ok(());
10583 }
10584
10585 if left_type.is_measure() && right_type.is_measure() {
10591 if let (
10592 ArithmeticComputation::Multiply | ArithmeticComputation::Divide,
10593 Some(left_decomposition),
10594 Some(right_decomposition),
10595 ) = (
10596 operator,
10597 left_type.measure_type_decomposition(),
10598 right_type.measure_type_decomposition(),
10599 ) {
10600 if semantics::combine_decompositions(
10601 left_decomposition,
10602 right_decomposition,
10603 matches!(operator, ArithmeticComputation::Multiply),
10604 )
10605 .is_none()
10606 {
10607 return Err(vec![engine_error_at_graph(
10608 graph,
10609 source,
10610 format!(
10611 "Cannot apply '{}' to {} and {}: a resulting unit exponent exceeds the maximum of {}.",
10612 operator,
10613 left_type.name(),
10614 right_type.name(),
10615 MAX_UNIT_EXPONENT
10616 ),
10617 )]);
10618 }
10619 }
10620 return match operator {
10621 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
10622 if left_type.same_measure_family(right_type)
10623 || left_type.compatible_with_anonymous_measure(right_type)
10624 {
10625 Ok(())
10626 } else {
10627 Err(vec![engine_error_at_graph(
10628 graph,
10629 source,
10630 format!(
10631 "Cannot {} unrelated measure types: {} and {}.",
10632 if matches!(operator, ArithmeticComputation::Add) {
10633 "add"
10634 } else {
10635 "subtract"
10636 },
10637 left_type.name(),
10638 right_type.name()
10639 ),
10640 )])
10641 }
10642 }
10643 ArithmeticComputation::Multiply => {
10644 Ok(())
10647 }
10648 ArithmeticComputation::Divide => {
10649 Ok(())
10651 }
10652 ArithmeticComputation::Modulo | ArithmeticComputation::Power => {
10653 Err(vec![engine_error_at_graph(
10654 graph,
10655 source,
10656 format!(
10657 "Cannot apply '{}' to two measure values ({} and {}).",
10658 operator,
10659 left_type.name(),
10660 right_type.name()
10661 ),
10662 )])
10663 }
10664 };
10665 }
10666
10667 if left_type.is_duration_like() && right_type.is_duration_like() {
10669 return match operator {
10670 ArithmeticComputation::Add | ArithmeticComputation::Subtract => Ok(()),
10671 ArithmeticComputation::Divide => Ok(()),
10672 _ => Err(vec![engine_error_at_graph(
10673 graph,
10674 source,
10675 "Cannot multiply two duration values. Convert operands first: 'value as number'."
10676 .to_string(),
10677 )]),
10678 };
10679 }
10680
10681 if left_type.is_calendar_like() && right_type.is_calendar_like() {
10682 return match operator {
10683 ArithmeticComputation::Add | ArithmeticComputation::Subtract => Ok(()),
10684 ArithmeticComputation::Divide => Ok(()),
10685 _ => Err(vec![engine_error_at_graph(
10686 graph,
10687 source,
10688 "Cannot multiply two calendar values. Convert operands first: 'value as number'."
10689 .to_string(),
10690 )]),
10691 };
10692 }
10693
10694 if (left_type.is_duration_like() && right_type.is_calendar_like())
10695 || (left_type.is_calendar_like() && right_type.is_duration_like())
10696 {
10697 return Err(vec![engine_error_at_graph(
10698 graph,
10699 source,
10700 format!(
10701 "Cannot apply '{}' to {} and {}. Duration and calendar are unrelated types.",
10702 operator,
10703 left_type.name(),
10704 right_type.name()
10705 ),
10706 )]);
10707 }
10708
10709 let left_valid = left_type.is_measure()
10711 || left_type.is_number()
10712 || left_type.is_duration_like()
10713 || left_type.is_calendar_like()
10714 || left_type.is_ratio();
10715 let right_valid = right_type.is_measure()
10716 || right_type.is_number()
10717 || right_type.is_duration_like()
10718 || right_type.is_calendar_like()
10719 || right_type.is_ratio();
10720
10721 if !left_valid || !right_valid {
10722 return Err(vec![engine_error_at_graph(
10723 graph,
10724 source,
10725 format!(
10726 "Cannot apply '{}' to {} and {}.",
10727 operator,
10728 left_type.name(),
10729 right_type.name()
10730 ),
10731 )]);
10732 }
10733
10734 if matches!(operator, ArithmeticComputation::Modulo)
10737 && (operand_forbids_modulo(left_type) || operand_forbids_modulo(right_type))
10738 {
10739 return Err(vec![engine_error_at_graph(
10740 graph,
10741 source,
10742 format!(
10743 "Cannot apply modulo to {} and {}. `mod` is defined for numbers only.",
10744 left_type.name(),
10745 right_type.name()
10746 ),
10747 )]);
10748 }
10749
10750 if left_type.has_same_base_type(right_type) {
10752 return Ok(());
10753 }
10754
10755 struct OperandPair<'t> {
10756 left_type: &'t LemmaType,
10757 right_type: &'t LemmaType,
10758 }
10759 impl OperandPair<'_> {
10760 fn check(&self, a: impl Fn(&LemmaType) -> bool, b: impl Fn(&LemmaType) -> bool) -> bool {
10761 (a(self.left_type) && b(self.right_type)) || (b(self.left_type) && a(self.right_type))
10762 }
10763 }
10764 let pair = OperandPair {
10765 left_type,
10766 right_type,
10767 };
10768
10769 let allowed = match operator {
10770 ArithmeticComputation::Multiply => {
10771 pair.check(LemmaType::is_measure, LemmaType::is_number)
10772 || pair.check(LemmaType::is_measure, LemmaType::is_ratio)
10773 || pair.check(LemmaType::is_measure, LemmaType::is_duration_like_measure)
10774 || pair.check(LemmaType::is_measure, LemmaType::is_calendar_like)
10775 || pair.check(LemmaType::is_duration_like_measure, LemmaType::is_number)
10776 || pair.check(LemmaType::is_duration_like_measure, LemmaType::is_ratio)
10777 || pair.check(LemmaType::is_calendar_like, LemmaType::is_number)
10778 || pair.check(LemmaType::is_calendar_like, LemmaType::is_ratio)
10779 || pair.check(LemmaType::is_number, LemmaType::is_ratio)
10780 }
10781 ArithmeticComputation::Divide => {
10782 pair.check(LemmaType::is_measure, LemmaType::is_number)
10783 || (left_type.is_measure() && right_type.is_ratio())
10784 || pair.check(LemmaType::is_measure, LemmaType::is_duration_like_measure)
10785 || pair.check(LemmaType::is_measure, LemmaType::is_calendar_like)
10786 || (left_type.is_duration_like() && right_type.is_number())
10787 || (left_type.is_duration_like() && right_type.is_ratio())
10788 || (left_type.is_calendar_like() && right_type.is_number())
10789 || (left_type.is_calendar_like() && right_type.is_ratio())
10790 || (left_type.is_number() && right_type.is_duration_like())
10791 || (left_type.is_number() && right_type.is_calendar_like())
10792 || pair.check(LemmaType::is_number, LemmaType::is_ratio)
10793 }
10794 ArithmeticComputation::Add | ArithmeticComputation::Subtract => {
10795 if pair.check(LemmaType::is_ratio, |t: &LemmaType| {
10796 t.is_number() || t.is_measure() || t.is_duration_like() || t.is_calendar_like()
10797 }) {
10798 return Err(vec![engine_error_at_graph(
10799 graph,
10800 source,
10801 format!(
10802 "Cannot apply '{}' to {} and {}. \
10803 Adding or subtracting a ratio is ambiguous; \
10804 scale explicitly, e.g. 'price - discount * price' \
10805 or 'price * (100% - discount)'.",
10806 operator,
10807 left_type.name(),
10808 right_type.name(),
10809 ),
10810 )]);
10811 }
10812 false
10813 }
10814 ArithmeticComputation::Power => {
10815 let left_ok = left_type.is_number()
10819 || left_type.is_measure()
10820 || left_type.is_ratio()
10821 || left_type.is_duration_like();
10822 let right_ok = if left_type.is_measure() || left_type.is_duration_like() {
10823 right_type.is_number()
10824 } else {
10825 right_type.is_number() || right_type.is_ratio()
10826 };
10827 left_ok && right_ok
10828 }
10829 ArithmeticComputation::Modulo => left_type.is_number() && right_type.is_number(),
10830 };
10831
10832 if !allowed {
10833 return Err(vec![engine_error_at_graph(
10834 graph,
10835 source,
10836 format!(
10837 "Cannot apply '{}' to {} and {}.",
10838 operator,
10839 left_type.name(),
10840 right_type.name(),
10841 ),
10842 )]);
10843 }
10844
10845 Ok(())
10846}
10847
10848fn literal_has_explicit_unit(lit: &semantics::TypedLiteral) -> bool {
10849 match &lit.value {
10850 ValueKind::Measure(_) => {
10851 let sig = lit.lemma_type.measure_runtime_signature();
10852 sig.len() == 1 && sig[0].1 == 1 && !sig[0].0.is_empty()
10853 }
10854 ValueKind::Ratio(_) => lit.lemma_type.ratio_primary_unit().is_some(),
10855 _ => false,
10856 }
10857}
10858
10859fn has_explicit_unit(source: ExprRef<'_, ExpressionKind>) -> bool {
10860 match source.kind() {
10861 ExpressionKind::Literal(lit) => literal_has_explicit_unit(lit),
10862 ExpressionKind::UnitConversion(_, SemanticConversionTarget::Unit { .. }) => true,
10863 ExpressionKind::StepRounding {
10864 step: Some(step), ..
10865 } => match source.child(*step).kind() {
10866 ExpressionKind::Literal(lit) => literal_has_explicit_unit(lit),
10867 _ => false,
10868 },
10869 _ => false,
10870 }
10871}
10872
10873fn check_step_rounding(
10876 graph: &Graph,
10877 mode: semantics::RoundingMode,
10878 operand: ExprRef<'_, ExpressionKind>,
10879 operand_type: &LemmaType,
10880 step: Option<ExprRef<'_, ExpressionKind>>,
10881 step_type: Option<&Arc<LemmaType>>,
10882 source: &Source,
10883) -> Result<(), Vec<Error>> {
10884 if operand_type.vetoed() || operand_type.is_undetermined() {
10885 return Ok(());
10886 }
10887 let Some(step) = step else {
10888 if operand_type.is_number() {
10889 return Ok(());
10890 }
10891 let label = expression_suggestion_label(operand);
10892 if operand_type.is_anonymous_measure() {
10893 let decomposition = operand_type
10894 .measure_type_decomposition()
10895 .expect("BUG: anonymous measure carries a decomposition");
10896 return Err(vec![engine_error_at_graph(
10897 graph,
10898 source,
10899 format!(
10900 "Cannot use '{mode}' on '{label}'. Round to a step instead: write \
10901 '{label} -> {mode}' followed by a step in a unit with dimensions {decomposition:?}"
10902 ),
10903 )]);
10904 }
10905 if operand_type.is_measure() || operand_type.is_ratio() {
10906 let kind = if operand_type.is_measure() {
10907 "measure"
10908 } else {
10909 "ratio"
10910 };
10911 let unit = first_unit_suggestion(operand_type);
10912 return Err(vec![engine_error_at_graph(
10913 graph,
10914 source,
10915 format!(
10916 "Cannot use '{mode}' on {kind} '{}'. Round to a step instead, for example \
10917 '{label} -> {mode} 1 {unit}'",
10918 operand_type.name()
10919 ),
10920 )]);
10921 }
10922 return Err(vec![engine_error_at_graph(
10923 graph,
10924 source,
10925 format!(
10926 "Cannot use '{mode}' on {}; write '{label} -> {mode}' only for a number, \
10927 or give a step of the same kind",
10928 operand_type.name()
10929 ),
10930 )]);
10931 };
10932 let step_type = step_type
10933 .expect("BUG: step carries a type when present")
10934 .as_ref();
10935 if step_type.vetoed() || step_type.is_undetermined() {
10936 return Ok(());
10937 }
10938 let ExpressionKind::Literal(literal) = step.kind() else {
10939 return Err(vec![engine_error_at_graph(
10940 graph,
10941 source,
10942 format!(
10943 "The step of '-> {mode}' must be a literal, not '{}'",
10944 expression_suggestion_label(step)
10945 ),
10946 )]);
10947 };
10948 if !step_rounding_compatible(operand_type, step_type) {
10949 let hint = match (
10950 operand_type.is_anonymous_measure(),
10951 operand_type.measure_unit_names(),
10952 ) {
10953 (false, Some(units)) if !units.is_empty() => format!(
10954 ". Write the step in a unit of {}, for example '-> {mode} 1 {}'",
10955 operand_type.name(),
10956 units[0]
10957 ),
10958 _ => String::new(),
10959 };
10960 return Err(vec![engine_error_at_graph(
10961 graph,
10962 source,
10963 format!(
10964 "Cannot round {} to a step of {}{hint}",
10965 operand_type.name(),
10966 step_type.name()
10967 ),
10968 )]);
10969 }
10970 let positive = match &literal.value {
10971 ValueKind::Number(step)
10972 | ValueKind::Ratio(step)
10973 | ValueKind::Measure(step)
10974 | ValueKind::UnscaledMeasure {
10975 magnitude: step, ..
10976 } => step.numer_is_positive(),
10977 other => unreachable!("BUG: step type is number, ratio or measure, got {other:?}"),
10978 };
10979 if !positive {
10980 return Err(vec![engine_error_at_graph(
10981 graph,
10982 source,
10983 format!("The step of '-> {mode}' must be greater than zero"),
10984 )]);
10985 }
10986 Ok(())
10987}
10988
10989enum SuggestionPiece<'a> {
10990 Node(ExprRef<'a, ExpressionKind>),
10991 Text(String),
10992}
10993
10994fn arithmetic_chain_pieces<'a>(
10996 node: ExprRef<'a, ExpressionKind>,
10997 first: ExprId,
10998 steps: &[ast::ChainStep<ArithmeticComputation>],
10999 pending: &mut Vec<SuggestionPiece<'a>>,
11000) {
11001 for step in steps.iter().rev() {
11002 pending.push(SuggestionPiece::Node(node.child(step.operand)));
11003 pending.push(SuggestionPiece::Text(format!(" {} ", step.op)));
11004 }
11005 pending.push(SuggestionPiece::Node(node.child(first)));
11006}
11007
11008fn expression_suggestion_label(expression: ExprRef<'_, ExpressionKind>) -> String {
11009 suggestion_label(vec![SuggestionPiece::Node(expression)])
11010}
11011
11012fn chain_prefix_suggestion_label(
11013 expression: ExprRef<'_, ExpressionKind>,
11014 first: ExprId,
11015 prefix: &[ast::ChainStep<ArithmeticComputation>],
11016) -> String {
11017 let mut pending = Vec::new();
11018 arithmetic_chain_pieces(expression, first, prefix, &mut pending);
11019 suggestion_label(pending)
11020}
11021
11022fn suggestion_label(mut pending: Vec<SuggestionPiece<'_>>) -> String {
11023 use SuggestionPiece as Piece;
11024 let mut out = String::new();
11025 while let Some(piece) = pending.pop() {
11026 match piece {
11027 Piece::Text(text) => out.push_str(&text),
11028 Piece::Node(node) => match node.kind() {
11029 ExpressionKind::DataPath(p) => out.push_str(&p.data),
11030 ExpressionKind::RulePath(p) => out.push_str(&p.rule),
11031 ExpressionKind::SpecMember { base, path, name } => {
11032 pending.push(Piece::Text(format!(".{name}")));
11033 for segment in path.iter().rev() {
11034 pending.push(Piece::Text(format!(".{segment}")));
11035 }
11036 pending.push(Piece::Node(node.child(*base)));
11037 }
11038 ExpressionKind::Arithmetic { first, steps } => {
11039 arithmetic_chain_pieces(node, *first, steps, &mut pending);
11040 }
11041 ExpressionKind::UnitConversion(inner, target) => {
11042 pending.push(Piece::Text(format!(" as {target}")));
11043 pending.push(Piece::Node(node.child(*inner)));
11044 }
11045 ExpressionKind::Literal(lit) => match &lit.value {
11046 ValueKind::Number(n) => out.push_str(&n.display_str()),
11047 _ => out.push_str("<value>"),
11048 },
11049 _ => out.push_str("<expr>"),
11050 },
11051 }
11052 }
11053 out
11054}
11055
11056fn first_unit_suggestion(source_type: &LemmaType) -> String {
11057 source_type
11058 .measure_unit_names()
11059 .or_else(|| source_type.ratio_unit_names())
11060 .and_then(|names| names.first().map(|u| (*u).to_string()))
11061 .unwrap_or_else(|| "<unit>".to_string())
11062}
11063
11064fn lookup_unit_type(
11065 resolved_types: &ResolvedTypesMap,
11066 spec: &LemmaSpec,
11067 unit_name: &str,
11068) -> Option<Arc<LemmaType>> {
11069 find_types_by_spec(resolved_types, spec)
11070 .and_then(|dt| dt.unit_index.resolve(unit_name).ok())
11071 .map(|(_, owner)| owner)
11072}
11073
11074fn is_valid_range_span_unit(
11075 source_type: &LemmaType,
11076 unit_name: &str,
11077 unit_index: &UnitIndex,
11078) -> bool {
11079 let Ok((bare, target_type)) = unit_index.resolve(unit_name) else {
11080 return false;
11081 };
11082 let unit_name = bare.as_str();
11083 if source_type.is_date_range() {
11084 return target_type.is_duration_like() || target_type.is_calendar_like();
11085 }
11086 if source_type.is_time_range() {
11087 return target_type.is_duration_like();
11088 }
11089 if source_type.is_measure_range() {
11090 if source_type
11091 .measure_unit_names()
11092 .is_some_and(|names| names.contains(&unit_name))
11093 {
11094 return target_type.is_measure();
11095 }
11096 let TypeSpecification::MeasureRange { decomposition, .. } = &source_type.specifications
11097 else {
11098 unreachable!("BUG: is_measure_range without MeasureRange spec");
11099 };
11100 let Some(source_decomp) = decomposition else {
11101 return false;
11102 };
11103 return target_type.is_measure()
11104 && target_type
11105 .measure_type_decomposition()
11106 .is_some_and(|td| td == source_decomp);
11107 }
11108 if source_type.is_ratio_range() {
11109 return target_type.is_ratio();
11110 }
11111 false
11112}
11113
11114fn check_unit_conversion_types(
11115 graph: &Graph,
11116 source: ExprRef<'_, ExpressionKind>,
11117 source_type: &LemmaType,
11118 target: &SemanticConversionTarget,
11119 resolved_types: &ResolvedTypesMap,
11120 source_loc: &Source,
11121 spec: &LemmaSpec,
11122) -> Result<(), Vec<Error>> {
11123 if source_type.vetoed() {
11124 return Ok(());
11125 }
11126 if source_type.is_spec() {
11127 return Err(vec![engine_error_at_graph(
11128 graph,
11129 source_loc,
11130 format!(
11131 "spec instance is not a value here (got {}); read a field or use `is veto`",
11132 source_type.name()
11133 ),
11134 )]);
11135 }
11136 let unit_index = find_types_by_spec(resolved_types, spec)
11137 .map(|dt| &dt.unit_index)
11138 .expect("BUG: spec types missing during unit conversion check");
11139
11140 match target {
11141 SemanticConversionTarget::Type(ConversionTypeKind::Time) => {
11142 if source_type.is_date() || source_type.is_time() {
11143 Ok(())
11144 } else {
11145 Err(vec![engine_error_at_graph(
11146 graph,
11147 source_loc,
11148 format!("Cannot convert {} to time.", source_type.name()),
11149 )])
11150 }
11151 }
11152 SemanticConversionTarget::Type(ConversionTypeKind::Boolean) => {
11153 if source_type.is_boolean() {
11154 Ok(())
11155 } else {
11156 Err(vec![engine_error_at_graph(
11157 graph,
11158 source_loc,
11159 format!("Cannot convert {} to boolean.", source_type.name()),
11160 )])
11161 }
11162 }
11163 SemanticConversionTarget::Unit {
11164 unit_name,
11165 owning_type,
11166 } => {
11167 if source_type.is_number() {
11168 if owning_type_declares_unit_name(owning_type, unit_name) {
11169 return Ok(());
11170 }
11171 return Err(vec![engine_error_at_graph(
11172 graph,
11173 source_loc,
11174 format!("Unknown unit '{unit_name}' in spec '{}'.", spec.name),
11175 )]);
11176 }
11177 if source_type.is_measure() {
11178 let target_type = owning_type.as_ref();
11179 if source_type.same_measure_family(target_type)
11180 || source_type.compatible_with_anonymous_measure(target_type)
11181 || target_type.compatible_with_anonymous_measure(source_type)
11182 {
11183 return Ok(());
11184 }
11185 if !has_explicit_unit(source) {
11186 let expr_label = expression_suggestion_label(source);
11187 let first_unit = first_unit_suggestion(source_type);
11188 return Err(vec![engine_error_at_graph(
11189 graph,
11190 source_loc,
11191 format!(
11192 "Cannot convert '{}' to '{unit_name}' (different measure families). \
11193 Express the source unit first, for example '{expr_label} as {first_unit} as {unit_name}'.",
11194 source_type.name()
11195 ),
11196 )]);
11197 }
11198 return Ok(());
11199 }
11200 if source_type.is_date_range()
11201 || source_type.is_time_range()
11202 || source_type.is_measure_range()
11203 || source_type.is_ratio_range()
11204 {
11205 if is_valid_range_span_unit(source_type, unit_name, unit_index) {
11206 return Ok(());
11207 }
11208 return Err(vec![engine_error_at_graph(
11209 graph,
11210 source_loc,
11211 format!(
11212 "Cannot convert {} span to unit '{unit_name}'.",
11213 source_type.name()
11214 ),
11215 )]);
11216 }
11217 if source_type.is_calendar_like() {
11218 if owning_type_declares_unit_name(owning_type, unit_name) {
11219 return Ok(());
11220 }
11221 return Err(vec![engine_error_at_graph(
11222 graph,
11223 source_loc,
11224 format!("Cannot convert calendar to unit '{unit_name}'."),
11225 )]);
11226 }
11227 if source_type.is_ratio() {
11228 if owning_type_declares_unit_name(owning_type, unit_name) {
11231 return Ok(());
11232 }
11233 let valid = match &owning_type.specifications {
11234 TypeSpecification::Ratio { units, .. } => units
11235 .0
11236 .iter()
11237 .map(|u| u.name.as_str())
11238 .collect::<Vec<_>>()
11239 .join(", "),
11240 _ => String::new(),
11241 };
11242 return Err(vec![engine_error_at_graph(
11243 graph,
11244 source_loc,
11245 format!(
11246 "Unknown unit '{unit_name}' for ratio type '{}'. Valid units: {}",
11247 owning_type.name(),
11248 valid
11249 ),
11250 )]);
11251 }
11252 Err(vec![engine_error_at_graph(
11253 graph,
11254 source_loc,
11255 format!(
11256 "Cannot convert {} to unit '{unit_name}'.",
11257 source_type.name()
11258 ),
11259 )])
11260 }
11261 SemanticConversionTarget::Type(ConversionTypeKind::Number) => {
11262 if source_type.is_date_range()
11263 || source_type.is_time_range()
11264 || source_type.is_measure_range()
11265 {
11266 return Err(vec![engine_error_at_graph(
11267 graph,
11268 source_loc,
11269 format!(
11270 "Cannot use 'as number' on a {}. \
11271 Express the span in a unit first, for example '<range> as <unit> as number'.",
11272 source_type.name()
11273 ),
11274 )]);
11275 }
11276 if source_type.is_number_range() {
11277 return Ok(());
11278 }
11279 if source_type.is_ratio_range() || source_type.is_calendar_like_range() {
11280 return Err(vec![engine_error_at_graph(
11281 graph,
11282 source_loc,
11283 format!("Cannot convert {} to number.", source_type.name()),
11284 )]);
11285 }
11286 if source_type.is_anonymous_measure() {
11287 if let Some(decomp) = source_type.measure_type_decomposition() {
11288 if !decomp.is_empty() {
11289 return Err(vec![engine_error_at_graph(
11290 graph,
11291 source_loc,
11292 format!(
11293 "Cannot use 'as number' to strip an anonymous intermediate with unresolved \
11294 dimensions {:?}. Ensure all dimensions cancel before converting to number.",
11295 decomp
11296 ),
11297 )]);
11298 }
11299 }
11300 }
11301 if source_type.is_measure() && !source_type.is_anonymous_measure() {
11302 if !has_explicit_unit(source) {
11303 let expr_label = expression_suggestion_label(source);
11304 let first_unit = first_unit_suggestion(source_type);
11305 return Err(vec![engine_error_at_graph(
11306 graph,
11307 source_loc,
11308 format!(
11309 "Cannot use 'as number' on measure '{}' without a unit. \
11310 Express it in a unit first, for example '{expr_label} as {first_unit} as number'.",
11311 source_type.name()
11312 ),
11313 )]);
11314 }
11315 return Ok(());
11316 }
11317 if source_type.is_measure()
11318 || source_type.is_number()
11319 || source_type.is_duration_like()
11320 || source_type.is_calendar_like()
11321 || source_type.is_ratio()
11322 {
11323 Ok(())
11324 } else {
11325 Err(vec![engine_error_at_graph(
11326 graph,
11327 source_loc,
11328 format!("Cannot convert {} to number.", source_type.name()),
11329 )])
11330 }
11331 }
11332 SemanticConversionTarget::Type(target_kind)
11333 if source_type.matches_primitive_kind(target_kind.to_primitive_kind()) =>
11334 {
11335 Ok(())
11336 }
11337 SemanticConversionTarget::Type(target_kind) => Err(vec![engine_error_at_graph(
11338 graph,
11339 source_loc,
11340 format!(
11341 "Cannot convert {} to {:?}.",
11342 source_type.name(),
11343 target_kind
11344 ),
11345 )]),
11346 }
11347}
11348fn check_mathematical_operand(
11349 graph: &Graph,
11350 op: &semantics::MathematicalComputation,
11351 operand_type: &LemmaType,
11352 source: &Source,
11353) -> Result<(), Vec<Error>> {
11354 if operand_type.vetoed() {
11355 return Ok(());
11356 }
11357 if operand_type.is_spec() {
11358 return Err(vec![engine_error_at_graph(
11359 graph,
11360 source,
11361 format!(
11362 "spec instance is not a value here (got {}); read a field or use `is veto`",
11363 operand_type.name()
11364 ),
11365 )]);
11366 }
11367 if operand_type.is_number() {
11368 return Ok(());
11369 }
11370 if (operand_type.is_measure() || operand_type.is_ratio())
11371 && mathematical_computation_applies_to_magnitude(op)
11372 {
11373 return Ok(());
11374 }
11375 let message = if operand_type.is_measure() {
11376 format!(
11377 "Mathematical function '{op}' cannot be applied to {operand_type}; use 'as number' first"
11378 )
11379 } else {
11380 format!("Mathematical function '{op}' requires number operand, got {operand_type}")
11381 };
11382 Err(vec![engine_error_at_graph(graph, source, message)])
11383}
11384
11385fn check_all_rule_references_exist(graph: &Graph) -> Result<(), Vec<Error>> {
11387 let mut errors = Vec::new();
11388 for (rule_path, rule_node) in graph.rules() {
11389 for dependency in &rule_node.depends_on_rules {
11390 match graph.rule_presence(dependency) {
11391 RulePresence::Present | RulePresence::Dropped => {}
11392 RulePresence::Absent => {
11393 errors.push(engine_error_at_graph(
11394 graph,
11395 &rule_node.source,
11396 format!(
11397 "Rule '{}' references non-existent rule '{}'",
11398 rule_path.rule, dependency.rule
11399 ),
11400 ));
11401 }
11402 }
11403 }
11404 }
11405 if errors.is_empty() {
11406 Ok(())
11407 } else {
11408 Err(errors)
11409 }
11410}
11411
11412fn check_data_and_rule_name_collisions(graph: &Graph) -> Result<(), Vec<Error>> {
11414 let mut errors = Vec::new();
11415 for rule_path in graph.rules().keys() {
11416 let data_path = DataPath::new(rule_path.segments.clone(), rule_path.rule.clone());
11417 if graph.data().contains_key(&data_path) {
11418 let rule_node = graph.rules().get(rule_path).unwrap_or_else(|| {
11419 unreachable!(
11420 "BUG: rule '{}' missing from graph while validating name collisions",
11421 rule_path.rule
11422 )
11423 });
11424 errors.push(engine_error_at_graph(
11425 graph,
11426 &rule_node.source,
11427 format!(
11428 "Name collision: '{}' is defined as both a data and a rule",
11429 data_path
11430 ),
11431 ));
11432 }
11433 }
11434 if errors.is_empty() {
11435 Ok(())
11436 } else {
11437 Err(errors)
11438 }
11439}
11440
11441fn check_data_reference(
11443 data_path: &DataPath,
11444 graph: &Graph,
11445 data_source: &Source,
11446) -> Result<(), Vec<Error>> {
11447 if graph.data().contains_key(data_path) {
11448 return Ok(());
11449 }
11450 Err(vec![engine_error_at_graph(
11451 graph,
11452 data_source,
11453 format!("Unknown data reference '{}'", data_path),
11454 )])
11455}
11456
11457enum CheckStep {
11459 Report(Vec<Error>),
11461 Visit(ExprId),
11463}
11464
11465#[derive(Default)]
11467struct CheckScratch {
11468 pending: Vec<CheckStep>,
11469 steps: Vec<CheckStep>,
11470}
11471
11472struct CheckSteps<'s> {
11474 steps: &'s mut Vec<CheckStep>,
11475 errors: Vec<Error>,
11476}
11477
11478impl<'s> CheckSteps<'s> {
11479 fn new(steps: &'s mut Vec<CheckStep>) -> Self {
11481 Self {
11482 steps,
11483 errors: Vec::new(),
11484 }
11485 }
11486
11487 fn push(&mut self, error: Error) {
11488 self.errors.push(error);
11489 }
11490
11491 fn collect(&mut self, result: Result<(), Vec<Error>>) {
11492 if let Err(errs) = result {
11493 self.errors.extend(errs);
11494 }
11495 }
11496
11497 fn visit(&mut self, child: &ExprId) {
11498 self.flush();
11499 self.steps.push(CheckStep::Visit(*child));
11500 }
11501
11502 fn splice(&mut self, steps: Vec<CheckStep>) {
11503 self.flush();
11504 self.steps.extend(steps);
11505 }
11506
11507 fn flush(&mut self) {
11508 if !self.errors.is_empty() {
11509 self.steps
11510 .push(CheckStep::Report(std::mem::take(&mut self.errors)));
11511 }
11512 }
11513
11514 fn finish(mut self) {
11515 self.flush();
11516 }
11517}
11518
11519fn check_expression(
11524 expression: ExprRef<'_, ExpressionKind>,
11525 graph: &Graph,
11526 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
11527 resolved_types: &ResolvedTypesMap,
11528 spec: &LemmaSpec,
11529 cache: &mut InferExpressionTypeCache,
11530 scratch: &mut CheckScratch,
11531) -> Result<(), Vec<Error>> {
11532 let mut errors = Vec::new();
11533 let CheckScratch { pending, steps } = scratch;
11534 pending.push(CheckStep::Visit(expression.id));
11535 while let Some(step) = pending.pop() {
11536 match step {
11537 CheckStep::Report(found) => errors.extend(found),
11538 CheckStep::Visit(id) => {
11539 check_steps(
11540 expression.child(id),
11541 graph,
11542 inferred_types,
11543 resolved_types,
11544 spec,
11545 cache,
11546 steps,
11547 );
11548 pending.extend(steps.drain(..).rev());
11549 }
11550 }
11551 }
11552 if errors.is_empty() {
11553 Ok(())
11554 } else {
11555 Err(errors)
11556 }
11557}
11558
11559fn check_steps(
11561 expression: ExprRef<'_, ExpressionKind>,
11562 graph: &Graph,
11563 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
11564 resolved_types: &ResolvedTypesMap,
11565 spec: &LemmaSpec,
11566 cache: &mut InferExpressionTypeCache,
11567 steps: &mut Vec<CheckStep>,
11568) {
11569 let mut checks = CheckSteps::new(steps);
11570 match expression.kind() {
11571 ExpressionKind::Literal(_) => {}
11572
11573 ExpressionKind::DataPath(data_path) => {
11574 let data_source = expression
11575 .source()
11576 .expect("BUG: expression missing source in check_expression");
11577 checks.collect(check_data_reference(data_path, graph, data_source));
11578 }
11579
11580 ExpressionKind::RulePath(_) => {}
11581
11582 ExpressionKind::SpecMember { base, name, path } => {
11583 let expr_source = expression
11584 .source()
11585 .expect("BUG: expression missing source in check_expression");
11586 checks.visit(base);
11587 let base_type = infer_expression_type(
11588 expression.child(*base),
11589 graph,
11590 inferred_types,
11591 resolved_types,
11592 cache,
11593 );
11594 if !base_type.is_spec() && !base_type.is_undetermined() && !base_type.vetoed() {
11595 let message = match expression.child(*base).kind() {
11596 ExpressionKind::RulePath(rule) => {
11597 format!("rule '{rule}' does not return a spec")
11598 }
11599 _ => format!(
11600 "`.{name}` reads a field of a spec value, but the operand is {}",
11601 base_type.name()
11602 ),
11603 };
11604 checks.push(engine_error_at_graph(graph, expr_source, message));
11605 } else if base_type.is_spec() && row_member_end(graph, &base_type, path, name).is_none()
11606 {
11607 checks.push(engine_error_at_graph(
11608 graph,
11609 expr_source,
11610 format!(
11611 "no data, rule or uses '{name}' on spec {}",
11612 base_type.name()
11613 ),
11614 ));
11615 }
11616 }
11617
11618 ExpressionKind::RowField { .. } | ExpressionKind::RowValue { .. } => {}
11619
11620 ExpressionKind::Logical {
11621 connective,
11622 first,
11623 steps,
11624 } => {
11625 checks.visit(first);
11626 let mut left_type = infer_expression_type(
11627 expression.child(*first),
11628 graph,
11629 inferred_types,
11630 resolved_types,
11631 cache,
11632 );
11633 for step in steps {
11634 checks.visit(&step.operand);
11635 let right_type = infer_expression_type(
11636 expression.child(step.operand),
11637 graph,
11638 inferred_types,
11639 resolved_types,
11640 cache,
11641 );
11642 checks.collect(check_logical_operands(
11643 graph,
11644 *connective,
11645 &left_type,
11646 &right_type,
11647 &step.source_location,
11648 ));
11649 left_type = logical_connective_type(&left_type, &right_type);
11650 }
11651 }
11652
11653 ExpressionKind::LogicalNegation(operand, _) => {
11654 checks.visit(operand);
11655
11656 let operand_type = infer_expression_type(
11657 expression.child(*operand),
11658 graph,
11659 inferred_types,
11660 resolved_types,
11661 cache,
11662 );
11663 let expr_source = expression
11664 .source()
11665 .expect("BUG: expression missing source in check_expression");
11666 checks.collect(check_logical_operand(graph, &operand_type, expr_source));
11667 }
11668
11669 ExpressionKind::Comparison(left, op, right) => {
11670 checks.visit(left);
11671 checks.visit(right);
11672
11673 let left_type = infer_expression_type(
11674 expression.child(*left),
11675 graph,
11676 inferred_types,
11677 resolved_types,
11678 cache,
11679 );
11680 let right_type = infer_expression_type(
11681 expression.child(*right),
11682 graph,
11683 inferred_types,
11684 resolved_types,
11685 cache,
11686 );
11687 let expr_source = expression
11688 .source()
11689 .expect("BUG: expression missing source in check_expression");
11690 checks.collect(check_comparison_types(
11691 graph,
11692 &left_type,
11693 op,
11694 &right_type,
11695 expr_source,
11696 ));
11697 }
11698
11699 ExpressionKind::Arithmetic { first, steps } => {
11700 checks.visit(first);
11701 let scope = arithmetic_scope(resolved_types, graph);
11702 let mut left_type = infer_expression_type(
11703 expression.child(*first),
11704 graph,
11705 inferred_types,
11706 resolved_types,
11707 cache,
11708 );
11709 for (index, step) in steps.iter().enumerate() {
11710 let operator = &step.op;
11711 let right = &step.operand;
11712
11713 enum InlineConversionOutcome {
11728 CheckedInlineConversionValid,
11729 CheckedConversionErrorEmitted,
11730 NotHandledInline,
11731 }
11732
11733 let is_multiplicative = matches!(
11734 operator,
11735 ArithmeticComputation::Multiply
11736 | ArithmeticComputation::Divide
11737 | ArithmeticComputation::Modulo
11738 );
11739
11740 let right_outcome = if is_multiplicative {
11741 if let ExpressionKind::UnitConversion(
11742 inner_source,
11743 conversion_target @ SemanticConversionTarget::Unit { unit_name, .. },
11744 ) = expression.child(*right).kind()
11745 {
11746 checks.visit(inner_source);
11748
11749 let inner_type = infer_expression_type(
11750 expression.child(*inner_source),
11751 graph,
11752 inferred_types,
11753 resolved_types,
11754 cache,
11755 );
11756 let expr_source = &step.source_location;
11757
11758 let target_type_opt = lookup_unit_type(resolved_types, spec, unit_name);
11759
11760 if let Some(target_type) = &target_type_opt {
11761 let inner_is_valid_conversion_source = inner_type.is_measure()
11762 && (inner_type.same_measure_family(target_type)
11763 || inner_type.compatible_with_anonymous_measure(target_type)
11764 || target_type.compatible_with_anonymous_measure(&inner_type));
11765
11766 if inner_is_valid_conversion_source {
11767 checks.collect(check_unit_conversion_types(
11771 graph,
11772 expression.child(*inner_source),
11773 &inner_type,
11774 conversion_target,
11775 resolved_types,
11776 expr_source,
11777 spec,
11778 ));
11779 InlineConversionOutcome::CheckedInlineConversionValid
11780 } else if inner_type.is_measure() || inner_type.is_number() {
11781 let combined_type = compute_arithmetic_result_type(
11784 Arc::clone(&left_type),
11785 operator,
11786 Arc::clone(&inner_type),
11787 &arithmetic_scope(resolved_types, graph),
11788 );
11789 let combined_is_valid_conversion_source = combined_type
11790 .is_measure()
11791 && (combined_type.same_measure_family(target_type)
11792 || combined_type
11793 .compatible_with_anonymous_measure(target_type)
11794 || target_type
11795 .compatible_with_anonymous_measure(&combined_type));
11796
11797 if combined_is_valid_conversion_source {
11798 let inner_label = expression_suggestion_label(
11799 expression.child(*inner_source),
11800 );
11801 let left_label = chain_prefix_suggestion_label(
11802 expression,
11803 *first,
11804 &steps[..index],
11805 );
11806 checks.push(engine_error_at_graph(
11807 graph,
11808 expr_source,
11809 format!(
11810 "'as {unit_name}' converts '{inner_label}' here, not \
11811 the result of the expression. Write \
11812 '({left_label} {operator} {inner_label}) as {unit_name}' \
11813 to convert the result.",
11814 ),
11815 ));
11816 } else {
11817 checks.collect(check_unit_conversion_types(
11820 graph,
11821 expression.child(*inner_source),
11822 &inner_type,
11823 conversion_target,
11824 resolved_types,
11825 expr_source,
11826 spec,
11827 ));
11828 }
11829 InlineConversionOutcome::CheckedConversionErrorEmitted
11830 } else {
11831 InlineConversionOutcome::NotHandledInline
11834 }
11835 } else {
11836 InlineConversionOutcome::NotHandledInline
11839 }
11840 } else {
11841 InlineConversionOutcome::NotHandledInline
11842 }
11843 } else {
11844 InlineConversionOutcome::NotHandledInline
11845 };
11846
11847 match right_outcome {
11848 InlineConversionOutcome::NotHandledInline => {
11849 checks.visit(right);
11850 }
11851 InlineConversionOutcome::CheckedInlineConversionValid
11852 | InlineConversionOutcome::CheckedConversionErrorEmitted => {
11853 }
11855 }
11856
11857 let run_arithmetic_checks = matches!(
11858 right_outcome,
11859 InlineConversionOutcome::NotHandledInline
11860 | InlineConversionOutcome::CheckedInlineConversionValid
11861 );
11862
11863 if run_arithmetic_checks {
11864 let right_type = infer_expression_type(
11865 expression.child(*right),
11866 graph,
11867 inferred_types,
11868 resolved_types,
11869 cache,
11870 );
11871 let expr_source = &step.source_location;
11872 checks.collect(check_arithmetic_types(
11873 graph,
11874 &left_type,
11875 &right_type,
11876 operator,
11877 expr_source,
11878 ));
11879 checks.collect(arithmetic_plan_time_exactness_planning_errors(
11880 graph,
11881 expression.child(*right),
11882 &left_type,
11883 &right_type,
11884 operator,
11885 expr_source,
11886 ));
11887 if let Some(error) = measure_power_cap_error(
11888 graph,
11889 &left_type,
11890 operator,
11891 expression.child(*right),
11892 expr_source,
11893 ) {
11894 checks.push(error);
11895 }
11896 }
11897 let right_expr = expression.child(step.operand);
11898 let right_type =
11899 infer_expression_type(right_expr, graph, inferred_types, resolved_types, cache);
11900 left_type =
11901 arithmetic_step_type(left_type, operator, right_expr, right_type, &scope);
11902 }
11903 }
11904
11905 ExpressionKind::UnitConversion(source_expression, target) => {
11906 checks.visit(source_expression);
11907
11908 let source_type = infer_expression_type(
11909 expression.child(*source_expression),
11910 graph,
11911 inferred_types,
11912 resolved_types,
11913 cache,
11914 );
11915 let expr_source = expression
11916 .source()
11917 .expect("BUG: expression missing source in check_expression");
11918 checks.collect(check_unit_conversion_types(
11919 graph,
11920 expression.child(*source_expression),
11921 &source_type,
11922 target,
11923 resolved_types,
11924 expr_source,
11925 spec,
11926 ));
11927 }
11928
11929 ExpressionKind::MathematicalComputation(op, operand) => {
11930 checks.visit(operand);
11931
11932 let operand_type = infer_expression_type(
11933 expression.child(*operand),
11934 graph,
11935 inferred_types,
11936 resolved_types,
11937 cache,
11938 );
11939 let expr_source = expression
11940 .source()
11941 .expect("BUG: expression missing source in check_expression");
11942 checks.collect(check_mathematical_operand(
11943 graph,
11944 op,
11945 &operand_type,
11946 expr_source,
11947 ));
11948 }
11949
11950 ExpressionKind::StepRounding {
11951 operand,
11952 mode,
11953 step,
11954 } => {
11955 checks.visit(operand);
11956 if let Some(step) = step {
11957 checks.visit(step);
11958 }
11959 let operand_type = infer_expression_type(
11960 expression.child(*operand),
11961 graph,
11962 inferred_types,
11963 resolved_types,
11964 cache,
11965 );
11966 let step_type = step.map(|step| {
11967 infer_expression_type(
11968 expression.child(step),
11969 graph,
11970 inferred_types,
11971 resolved_types,
11972 cache,
11973 )
11974 });
11975 let expr_source = expression
11976 .source()
11977 .expect("BUG: expression missing source in check_expression");
11978 checks.collect(check_step_rounding(
11979 graph,
11980 *mode,
11981 expression.child(*operand),
11982 &operand_type,
11983 step.map(|step| expression.child(step)),
11984 step_type.as_ref(),
11985 expr_source,
11986 ));
11987 }
11988
11989 ExpressionKind::RangeBound(bound, operand) => {
11990 checks.visit(operand);
11991
11992 let operand_type = infer_expression_type(
11993 expression.child(*operand),
11994 graph,
11995 inferred_types,
11996 resolved_types,
11997 cache,
11998 );
11999 if !operand_type.vetoed() && !operand_type.is_undetermined() && !operand_type.is_range()
12000 {
12001 let expr_source = expression
12002 .source()
12003 .expect("BUG: expression missing source in check_expression");
12004 checks.push(engine_error_at_graph(
12005 graph,
12006 expr_source,
12007 format!(
12008 "'{bound}' requires a range, got type '{}'",
12009 operand_type.name()
12010 ),
12011 ));
12012 }
12013 }
12014
12015 ExpressionKind::Veto(_) => {}
12016
12017 ExpressionKind::ResultIsVeto(operand) => {
12018 checks.visit(operand);
12019 }
12020
12021 ExpressionKind::Now => {}
12022
12023 ExpressionKind::DateRelative(_, date_expr) => {
12024 checks.visit(date_expr);
12025
12026 let date_type = infer_expression_type(
12027 expression.child(*date_expr),
12028 graph,
12029 inferred_types,
12030 resolved_types,
12031 cache,
12032 );
12033 if !date_type.vetoed() && !date_type.is_date() {
12034 let expr_source = expression
12035 .source()
12036 .expect("BUG: expression missing source in check_expression");
12037 checks.push(engine_error_at_graph(
12038 graph,
12039 expr_source,
12040 format!(
12041 "Date sugar 'in past/future' requires a date expression, got type '{}'",
12042 date_type
12043 ),
12044 ));
12045 }
12046 }
12047
12048 ExpressionKind::DateCalendar(_, _, date_expr) => {
12049 checks.visit(date_expr);
12050
12051 let date_type = infer_expression_type(
12052 expression.child(*date_expr),
12053 graph,
12054 inferred_types,
12055 resolved_types,
12056 cache,
12057 );
12058 if !date_type.vetoed() && !date_type.is_date() {
12059 let expr_source = expression
12060 .source()
12061 .expect("BUG: expression missing source in check_expression");
12062 checks.push(engine_error_at_graph(
12063 graph,
12064 expr_source,
12065 format!(
12066 "Calendar sugar requires a date expression, got type '{}'",
12067 date_type
12068 ),
12069 ));
12070 }
12071 }
12072
12073 ExpressionKind::RangeLiteral(left, right) => {
12074 checks.visit(left);
12075 checks.visit(right);
12076
12077 let left_type = infer_expression_type(
12078 expression.child(*left),
12079 graph,
12080 inferred_types,
12081 resolved_types,
12082 cache,
12083 );
12084 let right_type = infer_expression_type(
12085 expression.child(*right),
12086 graph,
12087 inferred_types,
12088 resolved_types,
12089 cache,
12090 );
12091 let expr_source = expression
12092 .source()
12093 .expect("BUG: expression missing source in check_expression");
12094
12095 if !(left_type.vetoed() || right_type.vetoed()) {
12096 let inferred_range_type =
12097 infer_range_type_from_endpoint_types(&left_type, &right_type);
12098 if inferred_range_type.is_undetermined() {
12099 checks.push(engine_error_at_graph(
12100 graph,
12101 expr_source,
12102 format!(
12103 "Cannot create a range from {} and {}.",
12104 left_type.name(),
12105 right_type.name()
12106 ),
12107 ));
12108 } else if inferred_range_type.is_time_range() {
12109 if let (ExpressionKind::Literal(left_lit), ExpressionKind::Literal(right_lit)) = (
12110 expression.child(*left).kind(),
12111 expression.child(*right).kind(),
12112 ) {
12113 if let (ValueKind::Time(left_time), ValueKind::Time(right_time)) =
12114 (&left_lit.value, &right_lit.value)
12115 {
12116 if !time_range_endpoints_share_timezone(left_time, right_time) {
12117 checks.push(engine_error_at_graph(
12118 graph,
12119 expr_source,
12120 "Time range endpoints must use the same timezone".to_string(),
12121 ));
12122 }
12123 }
12124 }
12125 }
12126 }
12127 }
12128
12129 ExpressionKind::PastFutureRange(_, offset_expr) => {
12130 checks.visit(offset_expr);
12131
12132 let offset_type = infer_expression_type(
12133 expression.child(*offset_expr),
12134 graph,
12135 inferred_types,
12136 resolved_types,
12137 cache,
12138 );
12139 if !offset_type.vetoed()
12140 && !offset_type.is_duration_like()
12141 && !offset_type.is_calendar_like()
12142 {
12143 let expr_source = expression
12144 .source()
12145 .expect("BUG: expression missing source in check_expression");
12146 checks.push(engine_error_at_graph(
12147 graph,
12148 expr_source,
12149 format!(
12150 "Past/future range requires a duration or calendar expression, got type '{}'",
12151 offset_type.name()
12152 ),
12153 ));
12154 }
12155 }
12156
12157 ExpressionKind::RangeContainment(value, range) => {
12158 checks.visit(value);
12159 checks.visit(range);
12160
12161 let value_type = infer_expression_type(
12162 expression.child(*value),
12163 graph,
12164 inferred_types,
12165 resolved_types,
12166 cache,
12167 );
12168 let range_type = infer_expression_type(
12169 expression.child(*range),
12170 graph,
12171 inferred_types,
12172 resolved_types,
12173 cache,
12174 );
12175 let expr_source = expression
12176 .source()
12177 .expect("BUG: expression missing source in check_expression");
12178
12179 checks.collect(check_range_containment_types(
12180 graph,
12181 &value_type,
12182 &range_type,
12183 expr_source,
12184 ));
12185 }
12186
12187 ExpressionKind::Piecewise(arms) => {
12188 for (condition, result) in arms {
12189 checks.visit(condition);
12190 checks.visit(result);
12191 let condition_type = infer_expression_type(
12192 expression.child(*condition),
12193 graph,
12194 inferred_types,
12195 resolved_types,
12196 cache,
12197 );
12198 if !condition_type.vetoed() && !condition_type.is_boolean() {
12199 let expr_source = expression
12200 .child(*condition)
12201 .source()
12202 .expect("BUG: expression missing source in check_expression");
12203 checks.push(engine_error_at_graph(
12204 graph,
12205 expr_source,
12206 format!(
12207 "Piecewise condition must be boolean, got type '{}'",
12208 condition_type.name()
12209 ),
12210 ));
12211 }
12212 }
12213 }
12214
12215 ExpressionKind::ListCommand { list, op, .. } => {
12216 checks.visit(list);
12217 let list_type = infer_expression_type(
12218 expression.child(*list),
12219 graph,
12220 inferred_types,
12221 resolved_types,
12222 cache,
12223 );
12224 let expr_source = expression
12225 .source()
12226 .expect("BUG: expression missing source in check_expression");
12227 if !list_type.is_list() && !list_type.is_undetermined() && !list_type.vetoed() {
12228 checks.push(engine_error_at_graph(
12229 graph,
12230 expr_source,
12231 format!("`->` commands require a list, got {}", list_type.name()),
12232 ));
12233 }
12234 let mut typing = TypingScope {
12235 graph,
12236 inferred_types,
12237 resolved_types,
12238 cache,
12239 };
12240 checks.splice(check_list_op(
12241 op,
12242 expression,
12243 expression.child(*list),
12244 &list_type,
12245 &mut typing,
12246 expr_source,
12247 ));
12248 }
12249 }
12250
12251 checks.finish();
12252}
12253
12254fn check_list_op(
12255 op: &ListOp,
12256 node: ExprRef<'_, ExpressionKind>,
12257 list: ExprRef<'_, ExpressionKind>,
12258 list_type: &LemmaType,
12259 typing: &mut TypingScope<'_>,
12260 source: &Source,
12261) -> Vec<CheckStep> {
12262 let TypingScope {
12263 graph,
12264 inferred_types,
12265 resolved_types,
12266 ..
12267 } = *typing;
12268 if let TypeSpecification::List { element, key, .. } = &list_type.specifications {
12269 if let TypeSpecification::Spec { spec: identity } = &element.specifications {
12270 return check_instance_list_op(op, node, list, identity, key, typing, source);
12271 }
12272 }
12273 let mut steps = Vec::new();
12274 let mut checks = CheckSteps::new(&mut steps);
12275 match op {
12276 ListOp::First | ListOp::Last | ListOp::Count => {}
12277 ListOp::Find { keys } => {
12278 for key in keys {
12279 checks.visit(key);
12280 }
12281 if keys.len() != 1 {
12282 checks.push(engine_error_at_graph(
12283 graph,
12284 source,
12285 "numbered `-> find` takes one row number".to_string(),
12286 ));
12287 } else {
12288 checks.collect(check_numbered_find_key(node.child(keys[0]), graph, source));
12289 }
12290 }
12291 ListOp::Reduce { argument, .. } => {
12292 if let Some(argument) = argument {
12293 checks.visit(argument);
12294 checks.push(engine_error_at_graph(
12295 graph,
12296 source,
12297 "value lists do not take a field argument; write `fares -> sum`".to_string(),
12298 ));
12299 }
12300 }
12301 ListOp::Predicate { form, expr, .. } => {
12302 checks.visit(expr);
12303 if *form == ast::ListPredicateForm::Instance {
12304 checks.push(engine_error_at_graph(
12305 graph,
12306 source,
12307 "value-list `any`/`all`/`where` take a comparison (`>= 100 eur`), not a field expression"
12308 .to_string(),
12309 ));
12310 }
12311 }
12312 ListOp::Sort { argument, .. } => {
12313 if let Some(argument) = argument {
12314 checks.visit(argument);
12315 checks.push(engine_error_at_graph(
12316 graph,
12317 source,
12318 "value-list `sort` takes `asc` or `desc`, not a field".to_string(),
12319 ));
12320 }
12321 }
12322 ListOp::Top { n } => {
12323 checks.visit(n);
12324 checks.collect(check_top_count(
12325 node.child(*n),
12326 graph,
12327 inferred_types,
12328 resolved_types,
12329 source,
12330 typing.cache,
12331 ));
12332 }
12333 ListOp::With { rows } => {
12334 for row in rows {
12335 checks.visit(row);
12336 }
12337 }
12338 ListOp::WithRow { .. } => {
12339 unreachable!("BUG: `-> with field: value` converted on a value list")
12340 }
12341 ListOp::Key { .. } => {
12342 checks.push(engine_error_at_graph(
12343 graph,
12344 source,
12345 "`-> key` is for instance lists".to_string(),
12346 ));
12347 }
12348 }
12349 if list_type.is_list() {
12350 let element_type = list_element_type(list_type, graph, resolved_types);
12351 checks.collect(check_list_element_op(
12352 op,
12353 &element_type,
12354 node,
12355 typing,
12356 source,
12357 ));
12358 }
12359 checks.finish();
12360 steps
12361}
12362
12363fn check_top_count(
12364 n: ExprRef<'_, ExpressionKind>,
12365 graph: &Graph,
12366 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
12367 resolved_types: &ResolvedTypesMap,
12368 source: &Source,
12369 cache: &mut InferExpressionTypeCache,
12370) -> Result<(), Vec<Error>> {
12371 let error = |message: String| Err(vec![engine_error_at_graph(graph, source, message)]);
12372 match n.kind() {
12373 ExpressionKind::Literal(literal) => match &literal.value {
12374 ValueKind::Number(count) => {
12375 let reduced = RationalInteger::try_reduce_ref(count)
12376 .map_err(|failure: NumericFailure| {
12377 vec![engine_error_at_graph(graph, source, failure.to_string())]
12378 })?;
12379 if reduced.is_integer() && !reduced.numer_is_negative() {
12380 Ok(())
12381 } else {
12382 error(format!(
12383 "`top` takes a whole number >= 0, got {}",
12384 count.display_str()
12385 ))
12386 }
12387 }
12388 _ => error(format!("`top` takes a number, got {literal}")),
12389 },
12390 ExpressionKind::DataPath(_) | ExpressionKind::RulePath(_) => {
12391 let n_type = infer_expression_type(n, graph, inferred_types, resolved_types, cache);
12392 if n_type.is_number() || n_type.is_undetermined() || n_type.vetoed() {
12393 Ok(())
12394 } else {
12395 error(format!("`top` takes a number, got {}", n_type.name()))
12396 }
12397 }
12398 _ => error(
12399 "`top` takes a number literal or a data or rule reference; compute other counts in a rule"
12400 .to_string(),
12401 ),
12402 }
12403}
12404
12405fn check_list_element_op(
12407 op: &ListOp,
12408 element_type: &LemmaType,
12409 node: ExprRef<'_, ExpressionKind>,
12410 typing: &mut TypingScope<'_>,
12411 source: &Source,
12412) -> Result<(), Vec<Error>> {
12413 let graph = typing.graph;
12414 if element_type.is_undetermined() || element_type.vetoed() {
12415 return Ok(());
12416 }
12417 let error = |message: String| Err(vec![engine_error_at_graph(graph, source, message)]);
12418 let summable = element_type.is_numeric() || element_type.is_ratio();
12419 let ordered = summable || element_type.is_date() || element_type.is_time();
12420 match op {
12421 ListOp::Reduce {
12422 kind,
12423 argument: None,
12424 } => match kind {
12425 ListReduce::Sum | ListReduce::Avg if !summable => error(format!(
12426 "`{}` takes number, measure or ratio rows, got {}",
12427 kind.keyword(),
12428 element_type.name()
12429 )),
12430 ListReduce::Product if element_type.is_measure() => error(format!(
12431 "`product` of {} rows would have a unit that depends on the row count; multiply in a rule instead",
12432 element_type.name()
12433 )),
12434 ListReduce::Product if !(element_type.is_number() || element_type.is_ratio()) => {
12435 error(format!(
12436 "`product` takes number or ratio rows, got {}",
12437 element_type.name()
12438 ))
12439 }
12440 ListReduce::Min | ListReduce::Max if !ordered => error(format!(
12441 "`{}` needs ordered rows (number, measure, ratio, date, time), got {}",
12442 kind.keyword(),
12443 element_type.name()
12444 )),
12445 _ => Ok(()),
12446 },
12447 ListOp::Sort { argument: None, .. } if !ordered => error(format!(
12448 "`sort` needs ordered rows (number, measure, ratio, date, time), got {}",
12449 element_type.name()
12450 )),
12451 ListOp::Predicate { expr, .. } => {
12452 let predicate_type = typing.infer(node.child(*expr));
12453 if predicate_type.is_boolean()
12454 || predicate_type.is_undetermined()
12455 || predicate_type.vetoed()
12456 {
12457 Ok(())
12458 } else {
12459 error(format!(
12460 "list predicate needs a boolean expression, got {}",
12461 predicate_type.name()
12462 ))
12463 }
12464 }
12465 ListOp::Find { keys } if keys.len() == 1 => {
12466 let key_type = typing.infer(node.child(keys[0]));
12467 if key_type.is_number() || key_type.is_undetermined() || key_type.vetoed() {
12468 Ok(())
12469 } else {
12470 error(format!(
12471 "numbered `find` takes a row number, got {}",
12472 key_type.name()
12473 ))
12474 }
12475 }
12476 _ => Ok(()),
12477 }
12478}
12479struct TypingScope<'t> {
12481 graph: &'t Graph<'t>,
12482 inferred_types: &'t HashMap<RulePath, Arc<LemmaType>>,
12483 resolved_types: &'t ResolvedTypesMap<'t>,
12484 cache: &'t mut InferExpressionTypeCache,
12485}
12486
12487impl TypingScope<'_> {
12488 fn infer(&mut self, expression: ExprRef<'_, ExpressionKind>) -> Arc<LemmaType> {
12489 infer_expression_type(
12490 expression,
12491 self.graph,
12492 self.inferred_types,
12493 self.resolved_types,
12494 self.cache,
12495 )
12496 }
12497
12498 fn error(&self, source: &Source, message: impl Into<String>) -> Error {
12499 engine_error_at_graph(self.graph, source, message)
12500 }
12501}
12502
12503fn check_instance_list_op(
12505 op: &ListOp,
12506 node: ExprRef<'_, ExpressionKind>,
12507 list: ExprRef<'_, ExpressionKind>,
12508 identity: &SpecIdentity,
12509 key: &ListKey,
12510 typing: &mut TypingScope<'_>,
12511 source: &Source,
12512) -> Vec<CheckStep> {
12513 let mut steps = Vec::new();
12514 let mut checks = CheckSteps::new(&mut steps);
12515 let Some(template) = row_fields(typing.graph, identity) else {
12516 checks.collect(Err(vec![typing.error(
12517 source,
12518 format!(
12519 "instance list of `{}` needs `uses {}` before `-> key` or `-> with`",
12520 identity.spec, identity.spec
12521 ),
12522 )]));
12523 checks.finish();
12524 return steps;
12525 };
12526 let row_spec = &identity.spec;
12527 match op {
12528 ListOp::First | ListOp::Last | ListOp::Count => {}
12529 ListOp::Find { keys } => {
12530 for key_expression in keys {
12531 checks.visit(key_expression);
12532 }
12533 checks.collect(check_instance_find(
12534 keys, node, key, template, typing, source,
12535 ));
12536 }
12537 ListOp::Reduce { kind, argument } => match argument {
12538 None => checks.collect(Err(vec![typing.error(
12539 source,
12540 format!(
12541 "`{}` on `{row_spec}` rows needs a field, for example `-> {} <field>`",
12542 kind.keyword(),
12543 kind.keyword()
12544 ),
12545 )])),
12546 Some(argument) => {
12547 checks.visit(argument);
12548 let argument_type = typing.infer(node.child(*argument));
12549 checks.collect(check_list_element_op(
12550 &ListOp::Reduce {
12551 kind: *kind,
12552 argument: None,
12553 },
12554 &argument_type,
12555 node,
12556 typing,
12557 source,
12558 ));
12559 }
12560 },
12561 ListOp::Sort {
12562 descending,
12563 argument,
12564 } => match argument {
12565 None => checks.collect(Err(vec![typing.error(
12566 source,
12567 format!("`sort` on `{row_spec}` rows needs a field, for example `-> sort <field>`"),
12568 )])),
12569 Some(argument) => {
12570 checks.visit(argument);
12571 let argument_type = typing.infer(node.child(*argument));
12572 checks.collect(check_list_element_op(
12573 &ListOp::Sort {
12574 descending: *descending,
12575 argument: None,
12576 },
12577 &argument_type,
12578 node,
12579 typing,
12580 source,
12581 ));
12582 }
12583 },
12584 ListOp::Predicate { kind, form, expr } => {
12585 checks.visit(expr);
12586 if *form == ast::ListPredicateForm::Value {
12587 checks.collect(Err(vec![typing.error(
12588 source,
12589 format!(
12590 "`{}` on `{row_spec}` rows takes a row expression such as `price > 3 eur`, not a bare comparison",
12591 kind.keyword()
12592 ),
12593 )]));
12594 }
12595 let condition_type = typing.infer(node.child(*expr));
12596 if !condition_type.is_boolean()
12597 && !condition_type.is_undetermined()
12598 && !condition_type.vetoed()
12599 {
12600 checks.collect(Err(vec![typing.error(
12601 source,
12602 format!(
12603 "`{}` on `{row_spec}` rows needs a boolean row expression, got {}",
12604 kind.keyword(),
12605 condition_type.name()
12606 ),
12607 )]));
12608 }
12609 }
12610 ListOp::Top { n } => {
12611 checks.visit(n);
12612 checks.collect(check_top_count(
12613 node.child(*n),
12614 typing.graph,
12615 typing.inferred_types,
12616 typing.resolved_types,
12617 source,
12618 typing.cache,
12619 ));
12620 }
12621 ListOp::With { rows } => {
12622 for row in rows {
12623 checks.visit(row);
12624 let value_type = typing.infer(node.child(*row));
12625 if value_type.is_undetermined() || value_type.vetoed() {
12626 continue;
12627 }
12628 let ok = match &value_type.specifications {
12629 TypeSpecification::Spec { spec } if spec == identity => true,
12630 TypeSpecification::List { element, .. } => {
12631 matches!(
12632 &element.specifications,
12633 TypeSpecification::Spec { spec } if spec == identity
12634 )
12635 }
12636 _ => false,
12637 };
12638 if !ok {
12639 checks.collect(Err(vec![typing.error(
12640 source,
12641 format!(
12642 "`-> with` on `{row_spec}` rows takes `field: value` pairs, a `{row_spec}` instance, or a `{row_spec}` list"
12643 ),
12644 )]));
12645 }
12646 }
12647 }
12648 ListOp::WithRow { fields, .. } => {
12649 let mut fields_typed = true;
12650 for (path, value) in fields {
12651 checks.visit(value);
12652 let field_type =
12653 template.end_type(&RowEnd::Data(path.clone()), typing.inferred_types);
12654 let value_type = typing.infer(node.child(*value));
12655 if !value_type.is_undetermined()
12656 && !value_type.vetoed()
12657 && !field_type.same_value_type(value_type.as_ref())
12658 {
12659 fields_typed = false;
12660 checks.collect(Err(vec![typing.error(
12661 source,
12662 format!(
12663 "row field `{}` of `{row_spec}` is {}, got {}",
12664 path.input_key(),
12665 field_type.name(),
12666 value_type.name()
12667 ),
12668 )]));
12669 }
12670 }
12671 if let ListKey::Fields(key_fields) = key {
12672 for key_field in key_fields {
12673 let path = &key_field.path;
12674 let written = fields.iter().any(|(written, _)| *written == *path);
12675 if !written && template.default(path).is_none() {
12676 checks.collect(Err(vec![typing.error(
12677 source,
12678 format!("`-> with` row must set key field `{}`", key_field.name),
12679 )]));
12680 }
12681 }
12682 }
12683 if fields_typed {
12684 checks.collect(check_appended_row_key(
12685 list, node, fields, key, typing, source,
12686 ));
12687 }
12688 }
12689 ListOp::Key { fields } => {
12690 checks.collect(check_key_list_spine(list, typing, source));
12691 for path in fields {
12692 assert!(
12693 path.segments.is_empty(),
12694 "BUG: `-> key` fields are names of the row spec"
12695 );
12696 let chain = template.chain(typing.graph, identity, &[path.data.as_str()]);
12697 let end = chain.as_deref().map(|chain| match chain {
12698 [hop] => &hop.end,
12699 _ => unreachable!("BUG: a one-part chain has one hop"),
12700 });
12701 let row_data = match &template {
12702 RowFields::Plan(plan) => &plan.data,
12703 RowFields::Root(graph) => graph.data(),
12704 };
12705 match end {
12706 Some(RowEnd::Data(data))
12707 if matches!(row_data.get(data), Some(DataDefinition::Import { .. })) =>
12708 {
12709 checks.collect(Err(vec![typing.error(
12710 source,
12711 format!(
12712 "`-> key` field `{}` must be data on `{row_spec}`, not a uses instance",
12713 path.input_key()
12714 ),
12715 )]))
12716 }
12717 Some(RowEnd::Data(_)) => {}
12718 Some(RowEnd::Rule(_)) => checks.collect(Err(vec![typing.error(
12719 source,
12720 format!(
12721 "`-> key` field `{}` must be data on `{row_spec}`, not a rule",
12722 path.input_key()
12723 ),
12724 )])),
12725 None => checks.collect(Err(vec![typing.error(
12726 source,
12727 format!(
12728 "`-> key` field `{}` is not a member of `{row_spec}`",
12729 path.input_key()
12730 ),
12731 )])),
12732 }
12733 }
12734 }
12735 }
12736 checks.finish();
12737 steps
12738}
12739
12740fn check_key_list_spine(
12743 list: ExprRef<'_, ExpressionKind>,
12744 typing: &TypingScope<'_>,
12745 source: &Source,
12746) -> Result<(), Vec<Error>> {
12747 match list.kind() {
12748 ExpressionKind::ListCommand { op, .. } => match op {
12749 ListOp::Key { .. } => Err(vec![typing.error(
12750 source,
12751 "`-> key` appears twice; list every key field in one `-> key`",
12752 )]),
12753 ListOp::With { .. } | ListOp::WithRow { .. } => Err(vec![typing.error(
12754 source,
12755 "`-> key` must come before the first `-> with`",
12756 )]),
12757 ListOp::Predicate { .. }
12758 | ListOp::Sort { .. }
12759 | ListOp::Top { .. }
12760 | ListOp::Reduce { .. }
12761 | ListOp::Find { .. }
12762 | ListOp::First
12763 | ListOp::Last
12764 | ListOp::Count => Err(vec![typing.error(
12765 source,
12766 "`-> key` declares list addressing; write it on the list before query commands such as `where`",
12767 )]),
12768 },
12769 _ => Ok(()),
12770 }
12771}
12772
12773fn check_instance_find(
12775 keys: &[ExprId],
12776 node: ExprRef<'_, ExpressionKind>,
12777 key: &ListKey,
12778 template: RowFields<'_>,
12779 typing: &mut TypingScope<'_>,
12780 source: &Source,
12781) -> Result<(), Vec<Error>> {
12782 let fields = match key {
12783 ListKey::Numbered => {
12784 if keys.len() != 1 {
12785 return Err(vec![
12786 typing.error(source, "numbered `-> find` takes one row number")
12787 ]);
12788 }
12789 check_numbered_find_key(node.child(keys[0]), typing.graph, source)?;
12790 let key_type = typing.infer(node.child(keys[0]));
12791 if key_type.is_number() || key_type.is_undetermined() || key_type.vetoed() {
12792 return Ok(());
12793 }
12794 return Err(vec![typing.error(
12795 source,
12796 format!(
12797 "numbered `find` takes a row number, got {}",
12798 key_type.name()
12799 ),
12800 )]);
12801 }
12802 ListKey::Fields(fields) => fields.as_slice(),
12803 };
12804 if keys.len() != fields.len() {
12805 return Err(vec![typing.error(
12806 source,
12807 format!(
12808 "`-> find` takes {} key fields ({}), got {}",
12809 fields.len(),
12810 fields
12811 .iter()
12812 .map(|field| field.name.as_str())
12813 .collect::<Vec<_>>()
12814 .join(", "),
12815 keys.len()
12816 ),
12817 )]);
12818 }
12819 let mut errors = Vec::new();
12820 for (key_expression, key_field) in keys.iter().zip(fields) {
12821 let key_expression = node.child(*key_expression);
12822 let end = RowEnd::Data(key_field.path.clone());
12823 let field_type = template.end_type(&end, typing.inferred_types);
12824 let key_type = typing.infer(key_expression);
12825 let result = if field_type.is_range() {
12826 check_range_containment_types(typing.graph, &key_type, &field_type, source)
12827 } else {
12828 check_comparison_types(
12829 typing.graph,
12830 &field_type,
12831 &ComparisonComputation::Is,
12832 &key_type,
12833 source,
12834 )
12835 };
12836 if let Err(found) = result {
12837 errors.push(typing.error(
12838 source,
12839 format!(
12840 "key field `{}` is {}, got {}: {}",
12841 key_field.name,
12842 field_type.name(),
12843 key_type.name(),
12844 found
12845 .iter()
12846 .map(|error| error.message().to_string())
12847 .collect::<Vec<_>>()
12848 .join("; ")
12849 ),
12850 ));
12851 }
12852 }
12853 if errors.is_empty() {
12854 Ok(())
12855 } else {
12856 Err(errors)
12857 }
12858}
12859
12860fn branch_type_label(lemma_type: &LemmaType) -> String {
12863 match &lemma_type.specifications {
12864 TypeSpecification::List {
12865 key: ListKey::Fields(fields),
12866 ..
12867 } => format!(
12868 "{} (key {})",
12869 lemma_type.name(),
12870 fields
12871 .iter()
12872 .map(|field| field.name.as_str())
12873 .collect::<Vec<_>>()
12874 .join(", ")
12875 ),
12876 TypeSpecification::List {
12877 key: ListKey::Numbered,
12878 ..
12879 } => format!("{} (numbered rows)", lemma_type.name()),
12880 _ => lemma_type.name().to_string(),
12881 }
12882}
12883
12884fn check_appended_row_key(
12885 list: ExprRef<'_, ExpressionKind>,
12886 node: ExprRef<'_, ExpressionKind>,
12887 fields: &[(DataPath, ExprId)],
12888 key: &ListKey,
12889 typing: &TypingScope<'_>,
12890 source: &Source,
12891) -> Result<(), Vec<Error>> {
12892 let ListKey::Fields(key_fields) = key else {
12893 return Ok(());
12894 };
12895 let ExpressionKind::DataPath(list_path) = list.kind() else {
12896 return Ok(());
12897 };
12898 let Some(list_source) = typing.graph.instance_lists().sources.get(list_path) else {
12899 return Ok(());
12900 };
12901 let Some(template) = row_fields(typing.graph, &list_source.identity) else {
12902 return Ok(());
12903 };
12904 let key_paths: Vec<DataPath> = key_fields
12905 .iter()
12906 .map(|key_field| key_field.path.clone())
12907 .collect();
12908 let mut appended = Vec::with_capacity(key_paths.len());
12909 for path in &key_paths {
12910 let written = fields
12911 .iter()
12912 .find(|(field, _)| field == path)
12913 .map(|(_, value)| value);
12914 let value = match written.map(|value| node.child(*value).kind()) {
12915 Some(ExpressionKind::Literal(literal)) => literal.as_ref().clone(),
12916 Some(_) => return Ok(()),
12917 None => match template.default(path) {
12918 Some(default) => default,
12919 None => return Ok(()),
12920 },
12921 };
12922 appended.push(value);
12923 }
12924 let store = &list_source.store;
12925 if store.key.is_empty() {
12926 return Ok(());
12928 }
12929 let stored = store.rows.len();
12930 let key_of = |row: usize| {
12931 if row == stored {
12932 appended.clone()
12933 } else {
12934 store.key_values(row)
12935 }
12936 };
12937 let built = KeyIndex::build(store.key_names(), stored + 1, |row, field| {
12938 if row == stored {
12939 &appended[field].value
12940 } else {
12941 store.key_value(row, field)
12942 }
12943 });
12944 match built {
12945 Ok(_) => Ok(()),
12946 Err((fields, conflict)) => {
12947 let message = conflict.message(&fields, |row| {
12948 let label = if row == stored {
12949 "the appended row".to_string()
12950 } else {
12951 format!("row {}", row + 1)
12952 };
12953 (label, key_of(row))
12954 });
12955 Err(vec![typing.error(source, message)])
12956 }
12957 }
12958}
12959
12960fn check_numbered_find_key(
12961 key: ExprRef<'_, ExpressionKind>,
12962 graph: &Graph,
12963 source: &Source,
12964) -> Result<(), Vec<Error>> {
12965 let ExpressionKind::Literal(literal) = key.kind() else {
12966 return Ok(());
12967 };
12968 let ValueKind::Number(n) = &literal.value else {
12969 return Ok(());
12970 };
12971 let decimal = n
12972 .try_to_decimal()
12973 .map_err(|failure| vec![engine_error_at_graph(graph, source, failure.to_string())])?;
12974 if decimal.fract() != Decimal::ZERO {
12975 return Err(vec![engine_error_at_graph(
12976 graph,
12977 source,
12978 format!("row number must be a whole number, got {decimal}"),
12979 )]);
12980 }
12981 if decimal <= Decimal::ZERO {
12982 return Err(vec![engine_error_at_graph(
12983 graph,
12984 source,
12985 format!("row number must be >= 1, got {decimal}"),
12986 )]);
12987 }
12988 Ok(())
12989}
12990
12991fn check_rule_types(
12997 graph: &Graph,
12998 rule_order: &[RulePath],
12999 inferred_types: &HashMap<RulePath, Arc<LemmaType>>,
13000 resolved_types: &ResolvedTypesMap,
13001 cache: &mut InferExpressionTypeCache,
13002) -> Result<(), Vec<Error>> {
13003 let mut errors = Vec::new();
13004 let mut scratch = CheckScratch::default();
13005
13006 let collect = |result: Result<(), Vec<Error>>, errors: &mut Vec<Error>| {
13007 if let Err(errs) = result {
13008 errors.extend(errs);
13009 }
13010 };
13011
13012 for rule_path in rule_order {
13013 let rule_node = graph
13014 .rules()
13015 .get(rule_path)
13016 .expect("BUG: rule from topological sort not in graph");
13017 let branches = &rule_node.branches;
13018 let spec = &rule_node.spec;
13019
13020 if branches.is_empty() {
13021 continue;
13022 }
13023
13024 let default_result = rule_node.expression.at(branches[0].1);
13025 collect(
13026 check_expression(
13027 default_result,
13028 graph,
13029 inferred_types,
13030 resolved_types,
13031 spec,
13032 cache,
13033 &mut scratch,
13034 ),
13035 &mut errors,
13036 );
13037 let default_type =
13038 infer_expression_type(default_result, graph, inferred_types, resolved_types, cache);
13039
13040 if default_type.is_anonymous_measure() {
13041 if let Some(decomp) = default_type.measure_type_decomposition() {
13042 if !decomp.is_empty() && anonymous_rule_boundary_requires_rejection() {
13043 let default_source = default_result
13044 .source()
13045 .expect("BUG: default branch result expression has no source location");
13046 errors.push(engine_error_at_graph(
13047 graph,
13048 default_source,
13049 anonymous_rule_boundary_error(
13050 rule_path,
13051 spec,
13052 graph,
13053 resolved_types,
13054 decomp,
13055 None,
13056 ),
13057 ));
13058 }
13059 }
13060 }
13061
13062 let mut non_veto_type: Option<LemmaType> = None;
13063 if !default_type.vetoed() && !default_type.is_undetermined() {
13064 non_veto_type = Some(default_type.as_ref().clone());
13065 }
13066
13067 for (branch_index, (condition, result)) in branches.iter().enumerate().skip(1) {
13068 let result = rule_node.expression.at(*result);
13069 if let Some(condition) = condition {
13070 let condition_expression = rule_node.expression.at(*condition);
13071 collect(
13072 check_expression(
13073 condition_expression,
13074 graph,
13075 inferred_types,
13076 resolved_types,
13077 spec,
13078 cache,
13079 &mut scratch,
13080 ),
13081 &mut errors,
13082 );
13083 let condition_type = infer_expression_type(
13084 condition_expression,
13085 graph,
13086 inferred_types,
13087 resolved_types,
13088 cache,
13089 );
13090 if !condition_type.vetoed()
13091 && !condition_type.is_boolean()
13092 && !condition_type.is_undetermined()
13093 {
13094 let condition_source = condition_expression
13095 .source()
13096 .expect("BUG: condition expression missing source in check_rule_types");
13097 errors.push(engine_error_at_graph(
13098 graph,
13099 condition_source,
13100 format!(
13101 "Unless clause condition in rule '{}' must be boolean, got {}",
13102 rule_path.rule, condition_type
13103 ),
13104 ));
13105 }
13106 }
13107
13108 collect(
13109 check_expression(
13110 result,
13111 graph,
13112 inferred_types,
13113 resolved_types,
13114 spec,
13115 cache,
13116 &mut scratch,
13117 ),
13118 &mut errors,
13119 );
13120 let result_type =
13121 infer_expression_type(result, graph, inferred_types, resolved_types, cache);
13122
13123 if result_type.is_anonymous_measure() {
13124 if let Some(decomp) = result_type.measure_type_decomposition() {
13125 if !decomp.is_empty() && anonymous_rule_boundary_requires_rejection() {
13126 let branch_source = result
13127 .source()
13128 .expect("BUG: unless branch result expression has no source location");
13129 errors.push(engine_error_at_graph(
13130 graph,
13131 branch_source,
13132 anonymous_rule_boundary_error(
13133 rule_path,
13134 spec,
13135 graph,
13136 resolved_types,
13137 decomp,
13138 Some(branch_index),
13139 ),
13140 ));
13141 }
13142 }
13143 }
13144
13145 if !result_type.vetoed() && !result_type.is_undetermined() {
13146 if non_veto_type.is_none() {
13147 non_veto_type = Some(result_type.as_ref().clone());
13148 } else if let Some(ref existing_type) = non_veto_type {
13149 if !existing_type.same_value_type(result_type.as_ref()) {
13150 let Some(rule_node) = graph.rules().get(rule_path) else {
13151 unreachable!(
13152 "BUG: rule type validation referenced missing rule '{}'",
13153 rule_path.rule
13154 );
13155 };
13156 let rule_source = &rule_node.source;
13157
13158 let mut location_parts = vec![format!(
13159 "{}:{}:{}",
13160 rule_source.source_type, rule_source.span.line, rule_source.span.col
13161 )];
13162
13163 if let Some(loc) = default_result.source() {
13164 location_parts.push(format!(
13165 "default branch at {}:{}:{}",
13166 loc.source_type, loc.span.line, loc.span.col
13167 ));
13168 }
13169 if let Some(loc) = result.source() {
13170 location_parts.push(format!(
13171 "unless clause {} at {}:{}:{}",
13172 branch_index, loc.source_type, loc.span.line, loc.span.col
13173 ));
13174 }
13175
13176 errors.push(Error::validation_with_context(
13177 format!("Type mismatch in rule '{}' in spec '{}' ({}): default branch returns {}, but unless clause {} returns {}. All branches must return the same value type (matching base kind and, for measures, dimensions).",
13178 rule_path.rule,
13179 spec.name,
13180 location_parts.join(", "),
13181 branch_type_label(existing_type),
13182 branch_index,
13183 branch_type_label(&result_type)),
13184 Some(rule_source.clone()),
13185 None::<String>,
13186 Some(graph.main_spec),
13187 None,
13188 ));
13189 }
13190 }
13191 }
13192 }
13193 }
13194
13195 if errors.is_empty() {
13196 Ok(())
13197 } else {
13198 Err(errors)
13199 }
13200}
13201
13202fn apply_inferred_types(graph: &mut Graph, inferred_types: HashMap<RulePath, Arc<LemmaType>>) {
13210 for (rule_path, rule_type) in inferred_types {
13211 if let Some(rule_node) = graph.rules_mut().get_mut(&rule_path) {
13212 rule_node.rule_type = rule_type;
13213 }
13214 }
13215}
13216
13217fn infer_rule_types(
13221 graph: &Graph,
13222 rule_order: &[RulePath],
13223 resolved_types: &ResolvedTypesMap,
13224 cache: &mut InferExpressionTypeCache,
13225) -> (HashMap<RulePath, Arc<LemmaType>>, Vec<Error>) {
13226 let mut computed_types: HashMap<RulePath, Arc<LemmaType>> = HashMap::new();
13227 let mut errors = Vec::new();
13228 let mut seeds: HashMap<RulePath, Arc<LemmaType>> = HashMap::new();
13229
13230 for rule_path in rule_order {
13232 let rule_node = graph
13233 .rules()
13234 .get(rule_path)
13235 .expect("BUG: rule from topological sort not in graph");
13236 if !rule_node.recursive || rule_node.branches.is_empty() {
13237 continue;
13238 }
13239 ensure_seed(rule_path, graph, &mut computed_types, resolved_types, cache);
13240 match computed_types.get(rule_path) {
13241 Some(seed) if !seed.vetoed() && !seed.is_undetermined() => {
13242 seeds.insert(rule_path.clone(), Arc::clone(seed));
13243 }
13244 _ => {
13245 errors.push(reference_error(
13246 graph.main_spec,
13247 &rule_node.source,
13248 format!(
13249 "recursive rule `{}` needs a non-recursive part to determine its type",
13250 rule_path.rule
13251 ),
13252 ));
13253 computed_types.insert(rule_path.clone(), Arc::new(LemmaType::undetermined_type()));
13254 }
13255 }
13256 cache.clear();
13257 }
13258
13259 let mut writes = 0usize;
13263 let mut written_at: HashMap<&RulePath, usize> = HashMap::new();
13264 let mut inferred_at: HashMap<&RulePath, usize> = HashMap::new();
13265 let mut overwrote_pass_one_entry = false;
13266 for rule_path in rule_order {
13267 let rule_node = graph
13268 .rules()
13269 .get(rule_path)
13270 .expect("BUG: rule from topological sort not in graph");
13271 if rule_node.branches.is_empty() {
13272 continue;
13273 }
13274 if rule_node.recursive && !seeds.contains_key(rule_path) {
13275 continue;
13276 }
13277 inferred_at.insert(rule_path, writes);
13278 let rule_type =
13279 infer_rule_body_type(rule_node, graph, &computed_types, resolved_types, cache);
13280 if let Some(seed) = seeds.get(rule_path) {
13281 let determined = !rule_type.vetoed() && !rule_type.is_undetermined();
13282 if determined && !rule_type.same_value_type(seed) {
13283 errors.push(reference_error(
13284 graph.main_spec,
13285 &rule_node.source,
13286 format!(
13287 "recursive rule `{}` is {} from its non-recursive part, but {} as a whole",
13288 rule_path.rule,
13289 seed.name(),
13290 rule_type.name()
13291 ),
13292 ));
13293 }
13294 }
13295 writes += 1;
13296 written_at.insert(rule_path, writes);
13297 if computed_types
13298 .insert(rule_path.clone(), rule_type)
13299 .is_some()
13300 {
13301 overwrote_pass_one_entry = true;
13302 }
13303 }
13304
13305 if overwrote_pass_one_entry {
13310 for rule_path in rule_order {
13311 let rule_node = graph
13312 .rules()
13313 .get(rule_path)
13314 .expect("BUG: rule from topological sort not in graph");
13315 if rule_node.recursive || rule_node.branches.is_empty() {
13316 continue;
13317 }
13318 let inferred = *inferred_at
13319 .get(rule_path)
13320 .expect("BUG: pass 2 inferred every non-recursive rule with branches");
13321 let read_a_later_write = rule_node
13322 .branches
13323 .iter()
13324 .flat_map(|(_, result)| {
13325 local_rule_names_in(rule_node.expression.at(*result), graph)
13326 })
13327 .any(|read| written_at.get(&read).is_some_and(|at| *at > inferred));
13328 if !read_a_later_write {
13329 continue;
13330 }
13331 cache.clear();
13332 let rule_type =
13333 infer_rule_body_type(rule_node, graph, &computed_types, resolved_types, cache);
13334 if computed_types.get(rule_path) != Some(&rule_type) {
13335 writes += 1;
13336 written_at.insert(rule_path, writes);
13337 computed_types.insert(rule_path.clone(), rule_type);
13338 }
13339 }
13340 cache.clear();
13341 }
13342
13343 (computed_types, errors)
13344}
13345
13346fn infer_rule_body_type(
13347 rule_node: &RuleNode<'_>,
13348 graph: &Graph,
13349 computed_types: &HashMap<RulePath, Arc<LemmaType>>,
13350 resolved_types: &ResolvedTypesMap,
13351 cache: &mut InferExpressionTypeCache,
13352) -> Arc<LemmaType> {
13353 let body_types = rule_node.branches.iter().map(|(_, result)| {
13354 infer_expression_type(
13355 rule_node.expression.at(*result),
13356 graph,
13357 computed_types,
13358 resolved_types,
13359 cache,
13360 )
13361 });
13362 piecewise_type(body_types, &arithmetic_scope(resolved_types, graph))
13363}
13364
13365fn ensure_seed(
13368 rule: &RulePath,
13369 graph: &Graph,
13370 computed_types: &mut HashMap<RulePath, Arc<LemmaType>>,
13371 resolved_types: &ResolvedTypesMap,
13372 cache: &mut InferExpressionTypeCache,
13373) {
13374 if computed_types.contains_key(rule) {
13375 return;
13376 }
13377 if !graph.rules().contains_key(rule) {
13378 return;
13379 }
13380 let mut reachable = Vec::new();
13381 let mut queued = HashSet::new();
13382 let mut pending = VecDeque::from([rule.clone()]);
13383 queued.insert(rule.clone());
13384 while let Some(path) = pending.pop_front() {
13385 let node = graph
13386 .rules()
13387 .get(&path)
13388 .expect("BUG: local rule missing from graph in ensure_seed");
13389 reachable.push(path);
13390 for (_, result) in &node.branches {
13391 for dependency in local_rule_names_in(node.expression.at(*result), graph) {
13392 if queued.insert(dependency.clone()) {
13393 pending.push_back(dependency);
13394 }
13395 }
13396 }
13397 }
13398 for path in &reachable {
13399 computed_types
13400 .entry(path.clone())
13401 .or_insert_with(|| Arc::new(LemmaType::undetermined_type()));
13402 }
13403 loop {
13404 let mut changed = false;
13405 for path in &reachable {
13406 let current = computed_types
13407 .get(path)
13408 .expect("BUG: reachable seed rule missing placeholder");
13409 if !current.vetoed() && !current.is_undetermined() {
13410 continue;
13411 }
13412 let node = graph
13413 .rules()
13414 .get(path)
13415 .expect("BUG: reachable seed rule missing from graph");
13416 cache.clear();
13417 let seed = node.branches.iter().find_map(|(_, result)| {
13418 seed_type(
13419 node.expression.at(*result),
13420 graph,
13421 computed_types,
13422 resolved_types,
13423 cache,
13424 )
13425 });
13426 if let Some(seed) = seed {
13427 computed_types.insert(path.clone(), seed);
13428 changed = true;
13429 }
13430 }
13431 if !changed {
13432 break;
13433 }
13434 }
13435}
13436
13437fn seed_type(
13440 expression: ExprRef<'_, ExpressionKind>,
13441 graph: &Graph,
13442 computed_types: &HashMap<RulePath, Arc<LemmaType>>,
13443 resolved_types: &ResolvedTypesMap,
13444 cache: &mut InferExpressionTypeCache,
13445) -> Option<Arc<LemmaType>> {
13446 let mut pending = vec![expression];
13447 while let Some(node) = pending.pop() {
13448 let (fixed, children) = seed_candidates(node);
13449 if let Some(fixed) = fixed {
13450 return Some(fixed);
13451 }
13452 if !children.is_empty() {
13453 pending.extend(children.into_iter().rev().map(|id| node.child(id)));
13455 continue;
13456 }
13457 match node.kind() {
13458 ExpressionKind::RulePath(rule) => {
13459 if let Some(lemma_type) = computed_types.get(rule) {
13460 if !lemma_type.vetoed() && !lemma_type.is_undetermined() {
13461 return Some(Arc::clone(lemma_type));
13462 }
13463 }
13464 }
13465 _ => {
13466 let inferred =
13467 infer_expression_type(node, graph, computed_types, resolved_types, cache);
13468 if !inferred.vetoed() && !inferred.is_undetermined() {
13469 return Some(inferred);
13470 }
13471 }
13472 }
13473 }
13474 None
13475}
13476
13477fn local_rule_names_in(node: ExprRef<'_, ExpressionKind>, graph: &Graph<'_>) -> Vec<RulePath> {
13479 let mut out = Vec::new();
13480 for index in 0..=node.id.index() {
13481 let child = node.child(ExprId::from_index(index));
13482 match child.kind() {
13483 ExpressionKind::RulePath(rule) => {
13484 if graph.rules().contains_key(rule) {
13485 out.push(rule.clone());
13486 }
13487 }
13488 ExpressionKind::RowField { end, .. } => {
13489 push_local_rule_from_row_end(graph, end, &mut out);
13490 }
13491 ExpressionKind::SpecMember { path, name, .. } => {
13492 let identity = &graph.instance_lists().self_identity;
13493 if let Some(fields) = row_fields(graph, identity) {
13494 let mut parts: Vec<&str> = path.iter().map(String::as_str).collect();
13495 parts.push(name);
13496 if let Some([hop]) = fields.chain(graph, identity, &parts).as_deref() {
13497 push_local_rule_from_row_end(graph, &hop.end, &mut out);
13498 }
13499 }
13500 }
13501 ExpressionKind::DataPath(path) => {
13502 if let Some(ReferenceEnd::Rule(rule)) = graph.reference_ends().get(path) {
13503 if graph.rules().contains_key(rule) {
13504 out.push(rule.clone());
13505 }
13506 } else if let Some(DataDefinition::Reference {
13507 target: ReferenceTarget::Rule(rule),
13508 ..
13509 }) = graph.data().get(path)
13510 {
13511 if graph.rules().contains_key(rule) {
13512 out.push(rule.clone());
13513 }
13514 }
13515 }
13516 _ => {}
13517 }
13518 }
13519 out
13520}
13521
13522fn push_local_rule_from_row_end(graph: &Graph<'_>, end: &RowEnd, out: &mut Vec<RulePath>) {
13523 match end {
13524 RowEnd::Rule(rule) => {
13525 if graph.rules().contains_key(rule) {
13526 out.push(rule.clone());
13527 }
13528 }
13529 RowEnd::Data(path) => {
13530 if let Some(DataDefinition::Reference {
13531 target: ReferenceTarget::Rule(rule),
13532 ..
13533 }) = graph.data().get(path)
13534 {
13535 if graph.rules().contains_key(rule) {
13536 out.push(rule.clone());
13537 }
13538 }
13539 }
13540 }
13541}
13542
13543fn seed_candidates(node: ExprRef<'_, ExpressionKind>) -> (Option<Arc<LemmaType>>, Vec<ExprId>) {
13546 match node.kind() {
13547 ExpressionKind::Arithmetic { first, steps }
13548 if steps.iter().all(|step| {
13549 matches!(
13550 step.op,
13551 ArithmeticComputation::Add | ArithmeticComputation::Subtract
13552 )
13553 }) =>
13554 {
13555 (
13556 None,
13557 std::iter::once(*first)
13558 .chain(steps.iter().map(|step| step.operand))
13559 .collect(),
13560 )
13561 }
13562 ExpressionKind::Comparison(..)
13563 | ExpressionKind::Logical { .. }
13564 | ExpressionKind::LogicalNegation(..) => {
13565 (Some(primitive_boolean_arc().clone()), Vec::new())
13566 }
13567 ExpressionKind::ListCommand { op, .. } => match op {
13568 ListOp::Count => (Some(primitive_number_arc().clone()), Vec::new()),
13569 ListOp::Predicate { kind, .. } => match kind {
13570 ListPredKind::Any | ListPredKind::All => {
13571 (Some(primitive_boolean_arc().clone()), Vec::new())
13572 }
13573 ListPredKind::Where => (None, Vec::new()),
13574 },
13575 ListOp::Reduce {
13576 kind: ListReduce::Sum | ListReduce::Min | ListReduce::Max | ListReduce::Avg,
13577 argument: Some(argument),
13578 } => (None, vec![*argument]),
13579 _ => (None, Vec::new()),
13580 },
13581 ExpressionKind::Piecewise(arms) => (None, arms.iter().map(|(_, result)| *result).collect()),
13582 _ => (None, Vec::new()),
13583 }
13584}
13585
13586type UnitDecompLookup = HashMap<String, (Option<String>, BaseMeasureVector, RationalInteger)>;
13588
13589fn local_owner_name(owner: &PlanningOwner) -> Option<String> {
13590 match owner {
13591 PlanningOwner::Local { type_name } => Some(type_name.clone()),
13592 PlanningOwner::PrimitiveRatio | PlanningOwner::Imported { .. } => None,
13593 }
13594}
13595
13596fn unwrap_unique_resolved_type(arc: Arc<LemmaType>) -> LemmaType {
13597 Arc::try_unwrap(arc).expect("BUG: resolved LemmaType Arc unique during type resolution")
13598}
13599
13600fn declared_measure_decomposition(lemma_type: &LemmaType) -> BaseMeasureVector {
13601 match &lemma_type.specifications {
13602 TypeSpecification::Measure { traits, .. }
13603 if traits.contains(&semantics::MeasureTrait::Duration) =>
13604 {
13605 duration_decomposition()
13606 }
13607 TypeSpecification::Measure { traits, .. }
13608 if traits.contains(&semantics::MeasureTrait::Calendar) =>
13609 {
13610 calendar_decomposition()
13611 }
13612 _ => {
13613 let family = lemma_type
13614 .measure_family()
13615 .expect("BUG: named measure type must carry a family");
13616 [(semantics::Dimension::Family(family.clone()), 1i32)]
13617 .into_iter()
13618 .collect()
13619 }
13620 }
13621}
13622
13623fn local_owning_measure_type_for_unit(
13625 unit_name: &str,
13626 lookup: &UnitDecompLookup,
13627 unit_index: &PlanningUnitIndex,
13628 resolved: &IndexMap<String, Arc<LemmaType>>,
13629) -> Option<String> {
13630 if let Some((owning_measure_name, _, _)) = lookup.get(unit_name) {
13631 return owning_measure_name.clone();
13632 }
13633 unit_index
13634 .resolve_owner(unit_name, resolved)
13635 .ok()
13636 .and_then(|(_, owner)| local_owner_name(owner))
13637}
13638
13639fn sort_derived_measure_types_for_resolution(
13641 spec_name: &str,
13642 derived_measure_type_names: Vec<String>,
13643 resolved: &IndexMap<String, Arc<LemmaType>>,
13644 lookup: &UnitDecompLookup,
13645 unit_index: &PlanningUnitIndex,
13646 type_sources: &HashMap<String, Source>,
13647) -> Result<Vec<String>, Error> {
13648 let derived_measure_type_count = derived_measure_type_names.len();
13649 if derived_measure_type_count == 0 {
13650 return Ok(derived_measure_type_names);
13651 }
13652
13653 let type_index: HashMap<&str, usize> = derived_measure_type_names
13654 .iter()
13655 .enumerate()
13656 .map(|(index, name)| (name.as_str(), index))
13657 .collect();
13658
13659 let mut dependency_sets: Vec<BTreeSet<usize>> =
13660 vec![BTreeSet::new(); derived_measure_type_count];
13661
13662 for (dependent_index, type_name) in derived_measure_type_names.iter().enumerate() {
13663 let TypeSpecification::Measure { units, .. } = &resolved[type_name].specifications else {
13664 continue;
13665 };
13666 for unit in units.iter() {
13667 for (factor_unit_name, _) in &unit.derived_measure_factors {
13668 let Some(owning_measure_name) = local_owning_measure_type_for_unit(
13669 factor_unit_name,
13670 lookup,
13671 unit_index,
13672 resolved,
13673 ) else {
13674 continue;
13675 };
13676 let Some(dependency_index) = type_index.get(owning_measure_name.as_str()).copied()
13677 else {
13678 continue;
13679 };
13680 if dependency_index == dependent_index {
13681 continue;
13682 }
13683 dependency_sets[dependent_index].insert(dependency_index);
13684 }
13685 }
13686 }
13687
13688 let mut in_degree = vec![0usize; derived_measure_type_count];
13689 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); derived_measure_type_count];
13690
13691 for (dependent_index, dependencies) in dependency_sets.iter().enumerate() {
13692 for &dependency_index in dependencies {
13693 in_degree[dependent_index] += 1;
13694 dependents[dependency_index].push(dependent_index);
13695 }
13696 }
13697
13698 let mut queue: VecDeque<usize> = (0..derived_measure_type_count)
13699 .filter(|&index| in_degree[index] == 0)
13700 .collect();
13701 let mut sorted_indices: Vec<usize> = Vec::with_capacity(derived_measure_type_count);
13702
13703 while let Some(index) = queue.pop_front() {
13704 sorted_indices.push(index);
13705 for &dependent_index in &dependents[index] {
13706 in_degree[dependent_index] -= 1;
13707 if in_degree[dependent_index] == 0 {
13708 queue.push_back(dependent_index);
13709 }
13710 }
13711 }
13712
13713 if sorted_indices.len() != derived_measure_type_count {
13714 let mut cycle_type_names: Vec<String> = (0..derived_measure_type_count)
13715 .filter(|&index| in_degree[index] > 0)
13716 .map(|index| derived_measure_type_names[index].clone())
13717 .collect();
13718 cycle_type_names.sort();
13719 return Err(Error::validation(
13720 format!(
13721 "In spec '{}': circular compound measure type dependency among: {}",
13722 spec_name,
13723 cycle_type_names.join(", ")
13724 ),
13725 type_sources.get(&cycle_type_names[0]).cloned(),
13726 None::<String>,
13727 ));
13728 }
13729
13730 Ok(sorted_indices
13731 .into_iter()
13732 .map(|index| derived_measure_type_names[index].clone())
13733 .collect())
13734}
13735
13736pub(crate) struct SignatureIndexBuild {
13742 pub index: SignatureIndex,
13743 pub deferred_cross_type: Vec<(String, BaseMeasureVector)>,
13744}
13745
13746pub(crate) fn build_signature_index(
13747 spec_name: &str,
13748 unit_index: &UnitIndex,
13749 already_resolved: &ResolvedTypesMap<'_>,
13750) -> Result<SignatureIndexBuild, Error> {
13751 use crate::computation::arithmetic::SignatureIndex;
13752 let mut signature_index: SignatureIndex = SignatureIndex::new();
13753 let mut deferred_cross_type = Vec::new();
13754 let mut sorted_units: Vec<_> = unit_index.iter_owners().collect();
13755 sorted_units.sort_by_key(|(name, _)| *name);
13756 for (unit_name, owner) in sorted_units {
13757 let unit_name = unit_name.to_string();
13758 let lemma_type = owner.lemma_type();
13759 let TypeSpecification::Measure { units, .. } = &lemma_type.specifications else {
13760 continue;
13761 };
13762 let unit = units.get(unit_name.as_str()).map_err(|_| {
13763 Error::validation(
13764 format!(
13765 "In spec '{}': unit_index entry '{}' is not declared on measure type '{}'",
13766 spec_name,
13767 unit_name,
13768 lemma_type.name()
13769 ),
13770 None::<Source>,
13771 None::<String>,
13772 )
13773 })?;
13774 if lemma_type.measure_type_decomposition().is_none() {
13775 continue;
13776 }
13777 if measure_unit_is_simple(unit) && unit_index.owners_for(&unit_name).len() != 1 {
13781 continue;
13782 }
13783 let owning_type_name = lemma_type.name.clone().unwrap_or_default();
13784 let signature = if owner.import_path().is_empty() {
13785 resolved_signature_key(
13786 &unit.derived_measure_factors,
13787 &unit_name,
13788 lemma_type,
13789 unit_index,
13790 )
13791 } else if let Some(key) = imported_signature_key(already_resolved, &unit_name, lemma_type) {
13792 key
13793 } else if measure_unit_is_simple(unit) {
13794 resolved_signature_key(
13797 &unit.derived_measure_factors,
13798 &unit_name,
13799 lemma_type,
13800 unit_index,
13801 )
13802 } else {
13803 continue;
13806 };
13807 match signature_index.get(&signature) {
13808 Some((existing_unit_name, existing_owning_type))
13809 if existing_owning_type.name() != lemma_type.name() =>
13810 {
13811 let existing_type_name = existing_owning_type.name.clone().unwrap_or_default();
13812 let decomposition = lemma_type
13813 .measure_type_decomposition()
13814 .expect("BUG: indexed measure unit has no decomposition")
13815 .clone();
13816 deferred_cross_type.push((
13817 format!(
13818 "In spec '{spec_name}': ambiguous unit signature {signature:?}: matched by '{existing_unit_name}' in type '{existing_type_name}' and '{unit_name}' in type '{owning_type_name}'. \
13819 Rename one or differentiate factors."
13820 ),
13821 decomposition,
13822 ));
13823 }
13824 _ => {
13825 signature_index.insert(signature, (unit_name.clone(), Arc::clone(lemma_type)));
13826 }
13827 }
13828 }
13829 Ok(SignatureIndexBuild {
13830 index: signature_index,
13831 deferred_cross_type,
13832 })
13833}
13834
13835fn resolved_signature_key(
13836 factors: &[(String, i32)],
13837 unit_name: &str,
13838 lemma_type: &LemmaType,
13839 unit_index: &UnitIndex,
13840) -> Vec<SignatureFactor> {
13841 use crate::computation::arithmetic::SignatureFactor;
13842 let mut key = Vec::with_capacity(factors.len());
13843 for (factor_name, exponent) in factors {
13844 let (name, family) = if factor_name == unit_name {
13845 (
13846 factor_name.clone(),
13847 lemma_type
13848 .measure_family()
13849 .expect("BUG: indexed measure unit has no family")
13850 .clone(),
13851 )
13852 } else {
13853 let (bare, owner) = unit_index.resolve_owner(factor_name).unwrap_or_else(|err| {
13854 panic!(
13855 "BUG: compound factor '{factor_name}' on unit '{unit_name}' must resolve: {err}"
13856 )
13857 });
13858 (
13859 bare,
13860 owner
13861 .lemma_type()
13862 .measure_family()
13863 .expect("BUG: resolved factor owner is not a named measure")
13864 .clone(),
13865 )
13866 };
13867 key.push(SignatureFactor {
13868 name,
13869 family,
13870 exponent: *exponent,
13871 });
13872 }
13873 key.sort_by(|left, right| {
13874 (&left.family, left.name.as_str(), left.exponent).cmp(&(
13875 &right.family,
13876 right.name.as_str(),
13877 right.exponent,
13878 ))
13879 });
13880 key
13881}
13882
13883fn imported_signature_key(
13886 already_resolved: &ResolvedTypesMap<'_>,
13887 unit_name: &str,
13888 lemma_type: &Arc<LemmaType>,
13889) -> Option<Vec<SignatureFactor>> {
13890 for (_, _, types) in already_resolved {
13891 for (key, (name, owner)) in &types.signature_index {
13892 if name == unit_name && Arc::ptr_eq(owner, lemma_type) {
13893 return Some(key.clone());
13894 }
13895 }
13896 }
13897 None
13898}
13899
13900fn deferred_signature_errors(
13901 graph: &Graph<'_>,
13902 resolved_types: &ResolvedTypesMap,
13903 inferred: &HashMap<RulePath, Arc<LemmaType>>,
13904) -> Vec<Error> {
13905 let Some(types) = resolved_types.iter().find_map(|(_, spec, types)| {
13906 discovery::same_loaded_spec(spec, graph.main_spec()).then_some(types)
13907 }) else {
13908 return Vec::new();
13909 };
13910 let mut errors = Vec::new();
13911 for (message, decomposition) in &types.deferred_signature_collisions {
13912 let produced = inferred.values().any(|lemma_type| {
13913 lemma_type.is_anonymous_measure()
13914 && lemma_type.measure_type_decomposition() == Some(decomposition)
13915 });
13916 if produced {
13917 continue;
13918 }
13919 errors.push(Error::validation(
13920 message.clone(),
13921 None::<Source>,
13922 None::<String>,
13923 ));
13924 }
13925 errors
13926}
13927
13928fn refresh_named_range_specs(
13931 resolver: &TypeResolver<'_>,
13932 spec: &LemmaSpec,
13933 data_defs: &HashMap<String, DataTypeDef>,
13934 resolved_types: &mut ResolvedSpecTypes,
13935 already_resolved: &ResolvedTypesMap<'_>,
13936 at: &EffectiveDate,
13937) -> Vec<Error> {
13938 let local = local_type_entries(resolved_types);
13939 let resolved = &mut resolved_types.resolved;
13940 let declared_suggestions = &mut resolved_types.declared_suggestions;
13941 let mut errors = Vec::new();
13942 for (type_name, def) in data_defs {
13943 let ParentType::Ranged { .. } = &def.parent else {
13944 continue;
13945 };
13946 let element = element_parent_type(&def.parent);
13947 if matches!(element, ParentType::Primitive { .. }) {
13948 continue;
13949 }
13950 let scope = ParentLookup {
13951 resolver,
13952 spec,
13953 source: &def.source,
13954 at,
13955 already_resolved,
13956 resolved: &local,
13957 site: TypeUseSite::DataDefinition,
13958 };
13959 let endpoint_type = match lookup_parent_type(&scope, element) {
13960 Ok((.., endpoint_type)) => endpoint_type,
13961 Err(lookup_errors) => {
13962 errors.extend(lookup_errors);
13963 continue;
13964 }
13965 };
13966 let range_spec = endpoint_type
13967 .specifications
13968 .range_from_element()
13969 .expect("BUG: ranged element was checked rangeable when the type was resolved");
13970 let lemma_type = resolved
13971 .get_mut(type_name.as_str())
13972 .expect("BUG: ranged type resolved before its range spec is refreshed");
13973 let mut updated = lemma_type.as_ref().clone();
13974 updated.specifications = range_spec;
13975 *lemma_type = Arc::new(updated);
13976
13977 if let Some(BoundValueKind {
13978 value: ValueKind::Range(left, right),
13979 ..
13980 }) = declared_suggestions.get_mut(type_name.as_str())
13981 {
13982 let left_typed = left.as_ref().clone();
13983 let right_typed = right.as_ref().clone();
13984 let coerced_left =
13985 match Graph::coerce_literal_to_schema_type(&left_typed, &endpoint_type) {
13986 Ok(coerced) => coerced,
13987 Err(message) => {
13988 errors.push(Error::validation_with_context(
13989 format!(
13990 "In spec '{}': named range default left endpoint for '{}': {message}",
13991 spec.name, type_name
13992 ),
13993 Some(def.source.clone()),
13994 None::<String>,
13995 Some(spec),
13996 None,
13997 ));
13998 continue;
13999 }
14000 };
14001 let coerced_right =
14002 match Graph::coerce_literal_to_schema_type(&right_typed, &endpoint_type) {
14003 Ok(coerced) => coerced,
14004 Err(message) => {
14005 errors.push(Error::validation_with_context(
14006 format!(
14007 "In spec '{}': named range default right endpoint for '{}': {message}",
14008 spec.name, type_name
14009 ),
14010 Some(def.source.clone()),
14011 None::<String>,
14012 Some(spec),
14013 None,
14014 ));
14015 continue;
14016 }
14017 };
14018 *declared_suggestions
14019 .get_mut(type_name.as_str())
14020 .expect("BUG: named range default removed while refreshing endpoints") =
14021 BoundValueKind {
14022 value: ValueKind::Range(Box::new(coerced_left), Box::new(coerced_right)),
14023 unit: None,
14024 };
14025 }
14026 }
14027 errors
14028}
14029
14030fn apply_deferred_named_range_constraints(
14032 spec: &LemmaSpec,
14033 data_defs: &HashMap<String, DataTypeDef>,
14034 resolved: &mut TypeMap,
14035 declared_suggestions: &mut IndexMap<String, BoundValueKind>,
14036 declared_values: &mut IndexMap<String, BoundValueKind>,
14037 type_sources: &HashMap<String, Source>,
14038 unit_index: &PlanningUnitIndex,
14039) -> Vec<Error> {
14040 let mut errors = Vec::new();
14041 for (type_name, def) in data_defs {
14042 if !should_defer_ranged_constraints(&def.parent) {
14043 continue;
14044 }
14045 let Some(constraints) = &def.constraints else {
14046 continue;
14047 };
14048 let Some(lemma_type) = resolved.get(type_name.as_str()).cloned() else {
14049 continue;
14050 };
14051 let mut declared_suggestion: Option<RawSuggestion> = None;
14052 let mut declared_value: Option<RawSuggestion> = None;
14053 let resolve = UnitTokenResolver::Planning {
14054 unit_index,
14055 named_types: resolved,
14056 };
14057 let mut updated = lemma_type.as_ref().clone();
14058 match apply_constraints_to_spec(
14059 spec,
14060 &constraint_application_type_name(&def.parent, type_name),
14061 type_name,
14062 &mut updated,
14063 constraints,
14064 &def.source,
14065 ConstraintSuggestionSink {
14066 suggestion: &mut declared_suggestion,
14067 value: &mut declared_value,
14068 resolve_unit: Some(&resolve),
14069 },
14070 ) {
14071 Ok(()) => {
14072 let updated_arc = Arc::new(updated);
14073 if let Some(raw) = declared_suggestion {
14074 match bound_value_kind_from_raw_suggestion(
14075 raw,
14076 &updated_arc.specifications,
14077 type_name.as_str(),
14078 ) {
14079 Ok(value_kind) => {
14080 declared_suggestions.insert(type_name.clone(), value_kind);
14081 }
14082 Err(message) => {
14083 let source = type_sources
14084 .get(type_name.as_str())
14085 .cloned()
14086 .unwrap_or_else(|| def.source.clone());
14087 errors.push(Error::validation_with_context(
14088 message,
14089 Some(source),
14090 None::<String>,
14091 Some(spec),
14092 None,
14093 ));
14094 }
14095 }
14096 }
14097 if let Some(raw) = declared_value {
14098 match bound_value_kind_from_raw_suggestion(
14099 raw,
14100 &updated_arc.specifications,
14101 type_name.as_str(),
14102 ) {
14103 Ok(value_kind) => {
14104 declared_values.insert(type_name.clone(), value_kind);
14105 }
14106 Err(message) => {
14107 let source = type_sources
14108 .get(type_name.as_str())
14109 .cloned()
14110 .unwrap_or_else(|| def.source.clone());
14111 errors.push(Error::validation_with_context(
14112 message,
14113 Some(source),
14114 None::<String>,
14115 Some(spec),
14116 None,
14117 ));
14118 }
14119 }
14120 }
14121 resolved.insert(type_name.clone(), updated_arc);
14122 }
14123 Err(constraint_errors) => errors.extend(constraint_errors),
14124 }
14125 }
14126 errors
14127}
14128
14129#[derive(Clone, Copy)]
14131enum TypeUseSite {
14132 DataDefinition,
14133 RuleListType,
14134}
14135
14136impl fmt::Display for TypeUseSite {
14137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
14138 match self {
14139 TypeUseSite::DataDefinition => write!(f, "data definition"),
14140 TypeUseSite::RuleListType => write!(f, "list type in a rule"),
14141 }
14142 }
14143}
14144
14145#[derive(Clone, Copy)]
14148struct ParentLookup<'s, 'a> {
14149 resolver: &'s TypeResolver<'a>,
14150 spec: &'s LemmaSpec,
14151 source: &'s Source,
14152 at: &'s EffectiveDate,
14153 already_resolved: &'s ResolvedTypesMap<'a>,
14154 resolved: &'s HashMap<String, ResolvedTypeEntry>,
14155 site: TypeUseSite,
14156}
14157
14158fn lookup_parent_type(
14159 scope: &ParentLookup<'_, '_>,
14160 parent: &ParentType,
14161) -> Result<ParentLookupOk, Vec<Error>> {
14162 let mut wrappers = Vec::new();
14163 let mut leaf = parent;
14164 while let ParentType::List { inner } | ParentType::Ranged { inner } = leaf {
14165 wrappers.push(leaf);
14166 leaf = inner.as_ref();
14167 }
14168 let mut lookup = lookup_leaf_parent_type(scope, leaf)?;
14169 while let Some(wrapper) = wrappers.pop() {
14170 lookup = lookup_wrapper_parent_type(scope, wrapper, lookup)?;
14171 }
14172 Ok(lookup)
14173}
14174
14175fn lookup_wrapper_parent_type(
14177 scope: &ParentLookup<'_, '_>,
14178 wrapper: &ParentType,
14179 inner_lookup: ParentLookupOk,
14180) -> Result<ParentLookupOk, Vec<Error>> {
14181 let ParentLookup { spec, source, .. } = *scope;
14182 let (element_specs, .., element_type) = inner_lookup;
14183 match wrapper {
14184 ParentType::List { inner } => {
14185 let element = if matches!(inner.as_ref(), ParentType::Ranged { .. }) {
14186 Arc::new(LemmaType::primitive(element_specs))
14187 } else {
14188 element_type
14189 };
14190 let lemma_type = Arc::new(LemmaType::list_of(element));
14191 Ok((lemma_type.specifications.clone(), None, None, lemma_type))
14192 }
14193 ParentType::Ranged { inner } => {
14194 let range_spec = element_specs.range_from_element().ok_or_else(|| {
14195 vec![Error::validation_with_context(
14196 format!(
14197 "'{inner}' is not rangeable: only measure, number, ratio, date, and time types support ranges"
14198 ),
14199 Some(source.clone()),
14200 None::<String>,
14201 Some(spec),
14202 None,
14203 )]
14204 })?;
14205 Ok((range_spec, None, None, element_type))
14206 }
14207 ParentType::Primitive { .. } | ParentType::Custom { .. } | ParentType::Qualified { .. } => {
14208 unreachable!("BUG: lookup_wrapper_parent_type called on a leaf parent")
14209 }
14210 }
14211}
14212
14213fn lookup_leaf_parent_type(
14215 scope: &ParentLookup<'_, '_>,
14216 parent: &ParentType,
14217) -> Result<ParentLookupOk, Vec<Error>> {
14218 let ParentLookup {
14219 resolver,
14220 spec,
14221 source,
14222 at,
14223 already_resolved,
14224 resolved,
14225 site,
14226 } = *scope;
14227 match parent {
14228 ParentType::List { .. } | ParentType::Ranged { .. } => {
14229 unreachable!("BUG: list and range wrappers are unwound before the leaf lookup")
14230 }
14231 ParentType::Primitive { primitive: kind } => {
14232 let lemma_type = Arc::clone(semantics::lemma_type_for_primitive(*kind));
14233 Ok((
14234 lemma_type.as_ref().specifications.clone(),
14235 None,
14236 None,
14237 lemma_type,
14238 ))
14239 }
14240 ParentType::Custom { name, .. } => {
14241 if let Some((parent_type, parent_suggestion, parent_value)) =
14242 resolved.get(name.as_str())
14243 {
14244 return Ok((
14245 parent_type.as_ref().specifications.clone(),
14246 parent_suggestion.clone(),
14247 parent_value.clone(),
14248 Arc::clone(parent_type),
14249 ));
14250 }
14251 let type_exists = resolver
14252 .data_types
14253 .iter()
14254 .find(|(s, _)| discovery::same_loaded_spec(s, spec))
14255 .map(|(_, type_map)| type_map.contains_key(name.as_str()))
14256 .unwrap_or(false);
14257 if !type_exists {
14258 if spec.data.iter().any(|data| {
14259 data.reference.is_local()
14260 && data.reference.name == name.as_str()
14261 && data.value.uses_spec_ref().is_some()
14262 }) {
14263 return Err(vec![Error::validation_with_context(
14264 format!(
14265 "'{name}' names a spec import alias, not a type. Use `data x: {name}.TypeName` after `uses`"
14266 ),
14267 Some(source.clone()),
14268 None::<String>,
14269 Some(spec),
14270 None,
14271 )]);
14272 }
14273 if spec.rules.iter().any(|rule| rule.name == name.as_str()) {
14274 return Err(vec![Error::validation_with_context(
14275 format!(
14276 "Unknown parent '{parent}' for {site}: '{name}' is a local rule, not a type. Use a type name, or a reference expression if you meant the rule's value."
14277 ),
14278 Some(source.clone()),
14279 None::<String>,
14280 Some(spec),
14281 None,
14282 )]);
14283 }
14284 let mut imported_aliases: Vec<&str> = Vec::new();
14285 for data_row in &spec.data {
14286 let Some(spec_ref) = data_row.value.uses_spec_ref() else {
14287 continue;
14288 };
14289 let import_alias = &data_row.reference.name;
14290 let (_, imported_spec) =
14291 match resolver.resolve_spec_for_import(spec, spec_ref, source, at) {
14292 Ok(pair) => pair,
14293 Err(_) => continue,
14294 };
14295 let Some(imported_resolved) = already_resolved
14296 .iter()
14297 .find(|(_, s, _)| discovery::same_loaded_spec(s, imported_spec))
14298 .map(|(_, _, r)| r)
14299 else {
14300 continue;
14301 };
14302 if imported_resolved.resolved.contains_key(name.as_str()) {
14303 imported_aliases.push(import_alias.as_str());
14304 }
14305 }
14306 if !imported_aliases.is_empty() {
14307 let forms = imported_aliases
14308 .iter()
14309 .map(|alias| format!("{alias}.{name}"))
14310 .collect::<Vec<_>>()
14311 .join(", ");
14312 return Err(vec![Error::validation_with_context(
14313 format!(
14314 "Unknown parent '{parent}' for {site}: '{name}' is declared by import{}; write {forms}",
14315 if imported_aliases.len() == 1 {
14316 format!(" '{}'", imported_aliases[0])
14317 } else {
14318 format!("s {}", imported_aliases.join(", "))
14319 }
14320 ),
14321 Some(source.clone()),
14322 None::<String>,
14323 Some(spec),
14324 None,
14325 )]);
14326 }
14327 if resolver
14328 .context
14329 .spec_set(resolver.registered_repository(spec), name)
14330 .is_some()
14331 {
14332 return Err(vec![Error::validation_with_context(
14333 format!(
14334 "Unknown parent '{parent}' for {site}: '{name}' is a spec, not a name in spec '{}'. Only the local names of `uses` lines exist inside a spec",
14335 spec.name
14336 ),
14337 Some(source.clone()),
14338 Some(format!(
14339 "Add `uses {name}`, or write the local name of the `uses` line that imports '{name}'."
14340 )),
14341 Some(spec),
14342 None,
14343 )]);
14344 }
14345 return Err(vec![Error::validation_with_context(
14346 format!(
14347 "Unknown parent '{parent}' for {site}. Parent must be defined before use. Valid primitive types are: boolean, measure, number, ratio, text, date, time"
14348 ),
14349 Some(source.clone()),
14350 None::<String>,
14351 Some(spec),
14352 None,
14353 )]);
14354 }
14355 Err(vec![Error::validation_with_context(
14356 format!(
14357 "Circular dependency detected in type resolution: {}::{}",
14358 spec.name, name
14359 ),
14360 Some(source.clone()),
14361 None::<String>,
14362 Some(spec),
14363 None,
14364 )])
14365 }
14366 ParentType::Qualified { spec_alias, inner } => {
14367 let import_row = spec
14368 .data
14369 .iter()
14370 .find(|row| row.reference.segments.is_empty() && row.reference.name == *spec_alias);
14371 let spec_ref = import_row.and_then(|row| {
14372 row.value
14373 .uses_spec_ref()
14374 .cloned()
14375 .or_else(|| discovery::data_instance_list_spec_ref(spec, row))
14376 });
14377 let Some(spec_ref) = spec_ref else {
14378 if spec.rules.iter().any(|rule| rule.name == *spec_alias) {
14379 return Err(vec![Error::validation_with_context(
14380 format!(
14381 "'{spec_alias}' is a local rule, not a type path. Row fields in type position name a `uses` import or a `data` instance list, not a rule."
14382 ),
14383 Some(source.clone()),
14384 None::<String>,
14385 Some(spec),
14386 None,
14387 )]);
14388 }
14389 return Err(vec![Error::validation_with_context(
14390 format!("'{spec_alias}' is not a `uses` import alias in this spec"),
14391 Some(source.clone()),
14392 Some(format!(
14393 "Add `uses {spec_alias}: <target_spec>`, then write `{spec_alias}.<type>`."
14394 )),
14395 Some(spec),
14396 None,
14397 )]);
14398 };
14399 let dynamic_effective = matches!(
14400 import_row.map(|row| &row.value),
14401 Some(ParsedDataValue::Import {
14402 effective_ref: Some(_),
14403 ..
14404 })
14405 );
14406 match inner.as_ref() {
14407 ParentType::Primitive { primitive: kind } => {
14408 let lemma_type = Arc::clone(semantics::lemma_type_for_primitive(*kind));
14409 Ok((
14410 lemma_type.as_ref().specifications.clone(),
14411 None,
14412 None,
14413 lemma_type,
14414 ))
14415 }
14416 ParentType::Custom { name, .. } if dynamic_effective => {
14417 union_dynamic_import_parent_type(
14418 resolver,
14419 spec,
14420 source,
14421 already_resolved,
14422 &spec_ref,
14423 spec_alias,
14424 name.as_str(),
14425 )
14426 }
14427 ParentType::Custom { name, .. } => {
14428 let (_, target_spec) =
14429 match resolver.resolve_spec_for_import(spec, &spec_ref, source, at) {
14430 Ok(import_pair) => import_pair,
14431 Err(error) => return Err(vec![error]),
14432 };
14433 let Some(resolved_spec_types) = already_resolved
14434 .iter()
14435 .find(|(_, imported, _)| discovery::same_loaded_spec(imported, target_spec))
14436 .map(|(_, _, resolved_spec_types)| resolved_spec_types)
14437 else {
14438 return Err(vec![Error::validation_with_context(
14439 format!(
14440 "Cannot resolve type '{name}' from spec '{}' (via import '{spec_alias}'): spec '{}' failed type resolution",
14441 target_spec.name, target_spec.name
14442 ),
14443 Some(source.clone()),
14444 None::<String>,
14445 Some(spec),
14446 None,
14447 )]);
14448 };
14449 let Some(parent_type) = resolved_spec_types.resolved.get(name.as_str()) else {
14450 let type_exists = resolver
14451 .data_types
14452 .iter()
14453 .find(|(s, _)| discovery::same_loaded_spec(s, target_spec))
14454 .map(|(_, type_map)| type_map.contains_key(name.as_str()))
14455 .unwrap_or(false);
14456 if !type_exists {
14457 return Err(vec![Error::validation_with_context(
14458 format!(
14459 "Type '{name}' is not defined in spec '{}' (via import '{spec_alias}')",
14460 target_spec.name
14461 ),
14462 Some(source.clone()),
14463 None::<String>,
14464 Some(spec),
14465 None,
14466 )]);
14467 }
14468 return Err(vec![Error::validation_with_context(
14469 format!(
14470 "Circular dependency detected in type resolution: {}::{}",
14471 target_spec.name, name
14472 ),
14473 Some(source.clone()),
14474 None::<String>,
14475 Some(spec),
14476 None,
14477 )]);
14478 };
14479 Ok((
14480 parent_type.as_ref().specifications.clone(),
14481 resolved_spec_types
14482 .source_defaults
14483 .get(name.as_str())
14484 .cloned(),
14485 resolved_spec_types
14486 .source_value_defaults
14487 .get(name.as_str())
14488 .cloned(),
14489 Arc::clone(parent_type),
14490 ))
14491 }
14492 ParentType::Qualified { .. } => Err(vec![Error::validation_with_context(
14493 "Nested qualified parent types are invalid",
14494 Some(source.clone()),
14495 None::<String>,
14496 Some(spec),
14497 None,
14498 )]),
14499 ParentType::Ranged { .. } => Err(vec![Error::validation_with_context(
14500 "Nested ranged parent types are invalid",
14501 Some(source.clone()),
14502 None::<String>,
14503 Some(spec),
14504 None,
14505 )]),
14506 ParentType::List { .. } => Err(vec![Error::validation_with_context(
14507 "Nested list parent types are invalid",
14508 Some(source.clone()),
14509 None::<String>,
14510 Some(spec),
14511 None,
14512 )]),
14513 }
14514 }
14515 }
14516}
14517
14518fn union_dynamic_import_parent_type(
14522 resolver: &TypeResolver<'_>,
14523 spec: &LemmaSpec,
14524 source: &Source,
14525 already_resolved: &ResolvedTypesMap<'_>,
14526 spec_ref: &ast::SpecRef,
14527 spec_alias: &str,
14528 type_name: &str,
14529) -> Result<ParentLookupOk, Vec<Error>> {
14530 let resolved_repo = match discovery::resolve_spec_ref_repository(
14531 resolver.context,
14532 spec_ref,
14533 Some(source),
14534 spec.name.as_str(),
14535 Some(spec),
14536 ) {
14537 Ok(resolved) => resolved,
14538 Err(error) => return Err(vec![error]),
14539 };
14540 let repository = match &resolved_repo.repository {
14541 Some(explicit) => Arc::clone(explicit),
14542 None => Arc::clone(resolver.registered_repository(spec)),
14543 };
14544 let dep_name = crate::parsing::ast::ascii_lowercase_logical_name(spec_ref.name.clone());
14545 let mut candidates: Vec<&Arc<LemmaType>> = Vec::new();
14546 for (repo, imported, resolved_types) in already_resolved {
14547 if !Arc::ptr_eq(repo, &repository) {
14548 continue;
14549 }
14550 if crate::parsing::ast::ascii_lowercase_logical_name(imported.name.clone()) != dep_name {
14551 continue;
14552 }
14553 let Some(parent_type) = resolved_types.resolved.get(type_name) else {
14554 let type_exists = resolver
14555 .data_types
14556 .iter()
14557 .find(|(s, _)| discovery::same_loaded_spec(s, imported))
14558 .map(|(_, type_map)| type_map.contains_key(type_name))
14559 .unwrap_or(false);
14560 if !type_exists {
14561 return Err(vec![Error::validation_with_context(
14562 format!(
14563 "Type '{type_name}' is not defined in spec '{}' (via import '{spec_alias}')",
14564 imported.name
14565 ),
14566 Some(source.clone()),
14567 None::<String>,
14568 Some(spec),
14569 None,
14570 )]);
14571 }
14572 return Err(vec![Error::validation_with_context(
14573 format!(
14574 "Circular dependency detected in type resolution: {}::{type_name}",
14575 imported.name
14576 ),
14577 Some(source.clone()),
14578 None::<String>,
14579 Some(spec),
14580 None,
14581 )]);
14582 };
14583 candidates.push(parent_type);
14584 }
14585 if candidates.is_empty() {
14586 return Err(vec![Error::validation_with_context(
14587 format!(
14588 "Cannot resolve type '{type_name}' from dependency '{}' (via import '{spec_alias}'): no candidate body has completed type resolution",
14589 spec_ref.name
14590 ),
14591 Some(source.clone()),
14592 None::<String>,
14593 Some(spec),
14594 None,
14595 )]);
14596 }
14597 let first = candidates[0];
14598 for later in &candidates[1..] {
14599 if !first.same_interface_ignoring_options(later) {
14600 return Err(vec![Error::validation_with_context(
14601 format!(
14602 "'{spec_alias}.{type_name}' changes across versions of '{}' under `-> effective`",
14603 spec_ref.name
14604 ),
14605 Some(source.clone()),
14606 Some(format!(
14607 "Keep '{type_name}' interface-compatible across every temporal version of '{}', or pin the import to a literal date.",
14608 spec_ref.name
14609 )),
14610 Some(spec),
14611 None,
14612 )]);
14613 }
14614 if first.options.rule != later.options.rule {
14615 return Err(vec![Error::validation_with_context(
14616 format!(
14617 "'{spec_alias}.{type_name}' has different `-> options` rules across versions of '{}' under `-> effective`",
14618 spec_ref.name
14619 ),
14620 Some(source.clone()),
14621 Some(
14622 "Use the same options rule name on every version, or only inline `-> option` values."
14623 .to_string(),
14624 ),
14625 Some(spec),
14626 None,
14627 )]);
14628 }
14629 }
14630 let mut unioned = first.as_ref().clone();
14631 let mut values: Vec<semantics::OptionValue> = Vec::new();
14632 for candidate in &candidates {
14633 for option in &candidate.options.values {
14634 if !values.iter().any(|seen| seen == option) {
14635 values.push(option.clone());
14636 }
14637 }
14638 }
14639 values.sort_by(|left, right| {
14640 left.value
14641 .value
14642 .to_string()
14643 .cmp(&right.value.value.to_string())
14644 .then_with(|| left.value.unit.cmp(&right.value.unit))
14645 });
14646 unioned.options.values = values;
14647 Ok((
14648 unioned.specifications.clone(),
14649 None,
14650 None,
14651 Arc::new(unioned),
14652 ))
14653}
14654
14655fn topo_sort_type_names(
14657 spec: &LemmaSpec,
14658 data_defs: &HashMap<String, DataTypeDef>,
14659 mut type_names: Vec<String>,
14660) -> Result<Vec<String>, Vec<Error>> {
14661 type_names.sort();
14662 if type_names.is_empty() {
14663 return Ok(type_names);
14664 }
14665 let type_index: HashMap<&str, usize> = type_names
14666 .iter()
14667 .enumerate()
14668 .map(|(index, name)| (name.as_str(), index))
14669 .collect();
14670 let type_count = type_names.len();
14671 let mut in_degree = vec![0usize; type_count];
14672 let mut dependents: Vec<Vec<usize>> = vec![Vec::new(); type_count];
14673 for (child_index, type_name) in type_names.iter().enumerate() {
14674 let custom_parent = match element_parent_type(&data_defs[type_name].parent) {
14675 ParentType::Custom { name, .. } => Some(name.as_str()),
14676 _ => None,
14677 };
14678 let Some(parent_name) = custom_parent else {
14679 continue;
14680 };
14681 let Some(&parent_index) = type_index.get(parent_name) else {
14682 continue;
14683 };
14684 if parent_index == child_index {
14685 continue;
14686 }
14687 in_degree[child_index] += 1;
14688 dependents[parent_index].push(child_index);
14689 }
14690 let mut queue: std::collections::VecDeque<usize> = (0..type_count)
14691 .filter(|&index| in_degree[index] == 0)
14692 .collect();
14693 let mut sorted = Vec::with_capacity(type_count);
14694 while let Some(index) = queue.pop_front() {
14695 sorted.push(type_names[index].clone());
14696 for &dependent_index in &dependents[index] {
14697 in_degree[dependent_index] -= 1;
14698 if in_degree[dependent_index] == 0 {
14699 queue.push_back(dependent_index);
14700 }
14701 }
14702 }
14703 if sorted.len() != type_count {
14704 let cycle_type_name = type_names
14705 .iter()
14706 .find(|name| in_degree[type_index[name.as_str()]] > 0)
14707 .expect("BUG: incomplete topo sort without cycle participant");
14708 return Err(vec![Error::validation_with_context(
14709 format!(
14710 "Circular dependency detected in type resolution: {}::{}",
14711 spec.name, cycle_type_name
14712 ),
14713 Some(data_defs[cycle_type_name].source.clone()),
14714 None::<String>,
14715 Some(spec),
14716 None,
14717 )]);
14718 }
14719 Ok(sorted)
14720}
14721
14722fn partition_list_type_names(
14727 data_defs: &HashMap<String, DataTypeDef>,
14728) -> (Vec<String>, Vec<String>) {
14729 fn has_list(parent: &ParentType) -> bool {
14730 let mut current = parent;
14731 loop {
14732 match current {
14733 ParentType::List { .. } => return true,
14734 ParentType::Ranged { inner } | ParentType::Qualified { inner, .. } => {
14735 current = inner.as_ref();
14736 }
14737 ParentType::Primitive { .. } | ParentType::Custom { .. } => return false,
14738 }
14739 }
14740 }
14741 let mut list_names: HashSet<String> = data_defs
14742 .iter()
14743 .filter(|(_, def)| has_list(&def.parent))
14744 .map(|(name, _)| name.clone())
14745 .collect();
14746 loop {
14747 let before = list_names.len();
14748 for (name, def) in data_defs {
14749 if let ParentType::Custom { name: parent, .. } = element_parent_type(&def.parent) {
14750 if list_names.contains(parent) {
14751 list_names.insert(name.clone());
14752 }
14753 }
14754 }
14755 if list_names.len() == before {
14756 break;
14757 }
14758 }
14759 let scalar_names = data_defs
14760 .keys()
14761 .filter(|name| !list_names.contains(*name))
14762 .cloned()
14763 .collect();
14764 (scalar_names, list_names.into_iter().collect())
14765}
14766
14767type TypeMap = IndexMap<String, Arc<LemmaType>>;
14768
14769fn local_data_or_rule(spec: &LemmaSpec, name: &str) -> bool {
14771 let is_data = spec
14772 .data
14773 .iter()
14774 .any(|d| d.reference.is_local() && d.reference.name == name);
14775 let is_rule = spec.rules.iter().any(|r| r.name == name);
14776 is_data || is_rule
14777}
14778
14779fn is_uses_alias(spec: &LemmaSpec, alias: &str) -> bool {
14781 spec.data.iter().any(|d| {
14782 d.reference.is_local()
14783 && d.reference.name == alias
14784 && matches!(d.value, ParsedDataValue::Import { .. })
14785 })
14786}
14787
14788fn classify_unit_payload_reference(
14793 reference: &ast::Reference,
14794 spec: &LemmaSpec,
14795 unit_index: &PlanningUnitIndex,
14796 resolved: &TypeMap,
14797) -> Result<UnitPayloadClass, String> {
14798 let local = reference.segments.is_empty() && local_data_or_rule(spec, &reference.name);
14799 let unit_ref = reference.to_string();
14800 let resolves_as_unit = unit_index
14801 .resolve_with_named_types(&unit_ref, resolved)
14802 .is_ok();
14803 if local && resolves_as_unit {
14804 return Err(format!(
14805 "unit scale reference '{reference}' is ambiguous: it names both a local data or rule and a unit path"
14806 ));
14807 }
14808 if local {
14809 return Ok(UnitPayloadClass::Scale);
14810 }
14811 if resolves_as_unit {
14812 return Ok(UnitPayloadClass::Compound);
14813 }
14814 if matches!(reference.segments.as_slice(), [alias] if is_uses_alias(spec, alias)) {
14815 return Ok(UnitPayloadClass::Scale);
14816 }
14817 Ok(UnitPayloadClass::Compound)
14818}
14819
14820enum UnitPayloadClass {
14821 Scale,
14822 Compound,
14823}
14824
14825fn rewrite_unit_path_scale_payloads(
14829 spec: &LemmaSpec,
14830 resolved: &mut TypeMap,
14831 unit_index: &PlanningUnitIndex,
14832 type_sources: &HashMap<String, Source>,
14833) -> Vec<Error> {
14834 let mut errors = Vec::new();
14835 let type_names: Vec<String> = resolved.keys().cloned().collect();
14836 for type_name in type_names {
14837 let lemma_type = resolved
14838 .get(&type_name)
14839 .expect("BUG: type_name comes from resolved keys");
14840 let TypeSpecification::Measure { units, .. } = &lemma_type.specifications else {
14841 continue;
14842 };
14843 let mut compounds: Vec<(usize, String)> = Vec::new();
14844 for (idx, unit) in units.iter().enumerate() {
14845 let Some((segments, name)) = unit.scale_reference_parts() else {
14846 continue;
14847 };
14848 if unit.resolved_scale_target().is_some() {
14849 continue;
14850 }
14851 let reference = ast::Reference {
14852 segments: segments.to_vec(),
14853 name: name.to_string(),
14854 };
14855 match classify_unit_payload_reference(&reference, spec, unit_index, resolved) {
14856 Ok(UnitPayloadClass::Scale) => {}
14857 Ok(UnitPayloadClass::Compound) => {
14858 compounds.push((idx, reference.to_string()));
14859 }
14860 Err(message) => {
14861 let source = type_sources.get(&type_name).cloned().unwrap_or_else(|| {
14862 panic!(
14863 "BUG: measure type '{type_name}' with a unit payload has no type source"
14864 )
14865 });
14866 errors.push(Error::validation(message, Some(source), None::<String>));
14867 }
14868 }
14869 }
14870 if compounds.is_empty() {
14871 continue;
14872 }
14873 let mut owned = lemma_type.as_ref().clone();
14874 let TypeSpecification::Measure { units, .. } = &mut owned.specifications else {
14875 unreachable!("BUG: measure type still a measure after clone");
14876 };
14877 for (idx, referenced) in compounds {
14878 let name = units.0[idx].name.clone();
14879 units.0[idx] =
14880 MeasureUnit::from_decimal_factor(name, Decimal::ONE, vec![(referenced, 1)])
14881 .expect("BUG: factor 1 always converts to a rational");
14882 }
14883 resolved.insert(type_name, Arc::new(owned));
14884 }
14885 errors
14886}
14887
14888fn resolve_measure_decompositions(
14889 spec_name: &str,
14890 this_spec: &SpecVersionKey,
14891 mut resolved: TypeMap,
14892 unit_index: &PlanningUnitIndex,
14893 type_sources: &HashMap<String, Source>,
14894) -> (TypeMap, Vec<Error>) {
14895 let mut errors: Vec<Error> = Vec::new();
14896
14897 let source_for = |type_name: &str| -> Option<Source> { type_sources.get(type_name).cloned() };
14898
14899 let base_type_names: Vec<String> = resolved
14900 .iter()
14901 .filter_map(|(name, lt)| {
14902 if let TypeSpecification::Measure { units, .. } = <.specifications {
14903 if units.iter().all(measure_unit_is_simple) {
14904 return Some(name.clone());
14905 }
14906 }
14907 None
14908 })
14909 .collect();
14910
14911 for type_name in &base_type_names {
14912 let owned = unwrap_unique_resolved_type(
14913 resolved
14914 .shift_remove(type_name)
14915 .expect("BUG: type_name comes from resolved's own keys"),
14916 );
14917 let base_decomp = declared_measure_decomposition(&owned);
14918
14919 let reserved_collision = reserved_calendar_units_in_measure(&owned);
14920 if !reserved_collision.is_empty() && !owned.has_trait_calendar() {
14921 errors.push(Error::validation(
14922 format!(
14923 "In spec '{}': measure type '{}' declares unit(s) {:?} which are reserved calendar unit names.",
14924 spec_name, type_name, reserved_collision
14925 ),
14926 source_for(type_name),
14927 None::<String>,
14928 ));
14929 resolved.insert(type_name.clone(), Arc::new(owned));
14930 continue;
14931 }
14932
14933 let updated = owned.map_measure(|units, _| {
14934 let units = units.map(|u| u.with_decomposition(base_decomp.clone()));
14935 (units, Some(base_decomp.clone()))
14936 });
14937 resolved.insert(type_name.clone(), Arc::new(updated));
14938 }
14939
14940 for lemma_type in resolved.values_mut() {
14941 let TypeSpecification::MeasureRange { units, .. } = &lemma_type.specifications else {
14942 continue;
14943 };
14944 if units.is_empty() {
14945 continue;
14946 }
14947 let family = lemma_type
14948 .family()
14949 .expect("BUG: measure range declaring units must be a named custom type");
14950 let range_decomp: BaseMeasureVector =
14951 [(semantics::Dimension::Family(family.clone()), 1i32)]
14952 .into_iter()
14953 .collect();
14954 let TypeSpecification::MeasureRange {
14955 units,
14956 decomposition,
14957 ..
14958 } = &mut Arc::get_mut(lemma_type)
14959 .expect("BUG: resolved LemmaType Arc unique during type resolution")
14960 .specifications
14961 else {
14962 unreachable!("BUG: matched MeasureRange above");
14963 };
14964 *units = std::mem::take(units).map(|u| u.with_decomposition(range_decomp.clone()));
14965 *decomposition = Some(range_decomp);
14966 }
14967
14968 let mut lookup = UnitDecompLookup::new();
14969
14970 for (unit_name, owner) in unit_index.iter_owners() {
14971 let unit_name = unit_name.to_string();
14972 let lemma_type = owner.lemma_type(&resolved);
14973 if let TypeSpecification::Measure {
14974 decomposition: Some(decomposition),
14975 units,
14976 ..
14977 } = &lemma_type.specifications
14978 {
14979 let measure_name = local_owner_name(owner);
14980 let unit = units
14981 .iter()
14982 .find(|u| u.name == unit_name)
14983 .unwrap_or_else(|| {
14984 panic!(
14985 "BUG: unit_name '{}' from unit_index must be in its owning type's units",
14986 unit_name
14987 )
14988 });
14989 let Some(factor) = unit.literal_factor().cloned() else {
14990 continue;
14991 };
14992 if unit_index.has_unique_owner(unit_name.as_str()) {
14994 lookup.insert(
14995 unit_name.clone(),
14996 (measure_name, decomposition.clone(), factor),
14997 );
14998 }
14999 }
15000 }
15001
15002 for (type_name, lemma_type) in resolved.iter() {
15003 if let TypeSpecification::Measure {
15004 units,
15005 decomposition: Some(decomposition),
15006 ..
15007 } = &lemma_type.specifications
15008 {
15009 let is_defining_type = lemma_type.measure_family().is_some_and(|family| {
15010 family.is_declared_in(this_spec) && family.name == *type_name
15011 });
15012 if !is_defining_type {
15013 continue;
15014 }
15015 for unit in units.iter() {
15016 let declared_by_other_family =
15017 unit_index.owners_for(&unit.name).iter().any(|owner| {
15018 !owner
15019 .lemma_type(&resolved)
15020 .same_measure_family(lemma_type.as_ref())
15021 });
15022 if declared_by_other_family {
15023 continue;
15024 }
15025 let Some(factor) = unit.literal_factor().cloned() else {
15026 continue;
15027 };
15028 lookup.insert(
15029 unit.name.clone(),
15030 (Some(type_name.clone()), decomposition.clone(), factor),
15031 );
15032 }
15033 }
15034 }
15035
15036 let derived_measure_type_names_unsorted: Vec<String> = resolved
15037 .iter()
15038 .filter_map(|(name, lemma_type)| {
15039 if let TypeSpecification::Measure { units, .. } = &lemma_type.specifications {
15040 if units.iter().any(|unit| !measure_unit_is_simple(unit)) {
15041 return Some(name.clone());
15042 }
15043 }
15044 None
15045 })
15046 .collect();
15047
15048 let derived_measure_type_names = match sort_derived_measure_types_for_resolution(
15049 spec_name,
15050 derived_measure_type_names_unsorted,
15051 &resolved,
15052 &lookup,
15053 unit_index,
15054 type_sources,
15055 ) {
15056 Ok(sorted) => sorted,
15057 Err(error) => {
15058 errors.push(error);
15059 return (resolved, errors);
15060 }
15061 };
15062
15063 for type_name in &derived_measure_type_names {
15064 let type_source = source_for(type_name);
15065
15066 let (units_snapshot, already_resolved_body) = match &resolved[type_name].specifications {
15067 TypeSpecification::Measure {
15068 units,
15069 decomposition,
15070 ..
15071 } => (units.clone(), decomposition.clone()),
15072 _ => continue,
15073 };
15074
15075 let body_already_resolved = already_resolved_body.is_some();
15079 let unit_owned_locally = |unit_name: &str| {
15080 unit_index.owners_for(unit_name).iter().any(|owner| {
15081 matches!(
15082 owner,
15083 PlanningOwner::Local {
15084 type_name: local_name
15085 } if local_name == type_name
15086 )
15087 })
15088 };
15089 if body_already_resolved && !units_snapshot.iter().any(|u| unit_owned_locally(&u.name)) {
15090 continue;
15091 }
15092
15093 let mut resolved_type_decomp: Option<BaseMeasureVector> = already_resolved_body;
15094 let mut unit_errors: Vec<Error> = Vec::new();
15095 let mut resolved_unit_factors: Vec<Option<RationalInteger>> =
15096 vec![None; units_snapshot.len()];
15097 let mut resolve_to_simple: Vec<bool> = vec![false; units_snapshot.len()];
15098
15099 for (unit_idx, unit) in units_snapshot.iter().enumerate() {
15100 if calendar_unit_factor(&unit.name).is_some()
15101 && !resolved[type_name].has_trait_calendar()
15102 {
15103 unit_errors.push(Error::validation(
15104 format!(
15105 "In spec '{}': measure type '{}' declares unit '{}' which is a reserved calendar unit name.",
15106 spec_name, type_name, unit.name
15107 ),
15108 type_source.clone(),
15109 None::<String>,
15110 ));
15111 continue;
15112 }
15113 if measure_unit_is_simple(unit) {
15114 let simple_decomp = declared_measure_decomposition(&resolved[type_name]);
15115
15116 if let Some(existing) = &resolved_type_decomp {
15117 if existing != &simple_decomp {
15118 unit_errors.push(Error::validation(
15119 format!(
15120 "In spec '{}': measure type '{}' has inconsistent unit decompositions. \
15121 Unit '{}' is a simple unit (decomposition {}) but other units \
15122 have decomposition {}.",
15123 spec_name,
15124 type_name,
15125 unit.name,
15126 semantics::format_signature_operator_style(
15127 &semantics::base_measure_vector_as_signature(&simple_decomp)
15128 ),
15129 semantics::format_signature_operator_style(
15130 &semantics::base_measure_vector_as_signature(existing)
15131 )
15132 ),
15133 type_source.clone(),
15134 None::<String>,
15135 ));
15136 }
15137 } else {
15138 resolved_type_decomp = Some(simple_decomp);
15139 }
15140
15141 if let Some(factor) = unit.literal_factor() {
15142 resolved_unit_factors[unit_idx] = Some(factor.clone());
15143 }
15144 continue;
15145 }
15146
15147 if body_already_resolved && !unit_owned_locally(&unit.name) {
15148 if let Some(factor) = unit.literal_factor() {
15149 resolved_unit_factors[unit_idx] = Some(factor.clone());
15150 }
15151 continue;
15152 }
15153
15154 match resolve_compound_unit(
15155 spec_name,
15156 type_name,
15157 unit,
15158 &lookup,
15159 unit_index,
15160 &resolved,
15161 type_source.as_ref(),
15162 ) {
15163 Ok((unit_decomp, derived_factor)) => {
15164 if let [(referenced, 1)] = unit.derived_measure_factors.as_slice() {
15165 let (bare_name, owner) = unit_index
15166 .resolve_owner(referenced, &resolved)
15167 .unwrap_or_else(|err| {
15168 panic!(
15169 "BUG: factor '{referenced}' resolved for its conversion but not for its owner: {err}"
15170 )
15171 });
15172 let owning_type = owner.lemma_type(&resolved);
15173 let owner_family = owning_type
15174 .measure_family()
15175 .expect("BUG: resolved unit owner is not a named measure");
15176 let declaring_family = resolved[type_name]
15177 .measure_family()
15178 .expect("BUG: declaring measure has no family");
15179 if owner_family != declaring_family {
15180 unit_errors.push(Error::validation(
15181 format!(
15182 "In spec '{spec_name}': unit '{}' in measure type '{type_name}' cannot use a single unit from another measure. '{bare_name}' belongs to '{}'.",
15183 unit.name, owner_family.name
15184 ),
15185 type_source.clone(),
15186 None::<String>,
15187 ));
15188 }
15189 }
15190 if unit_decomp == declared_measure_decomposition(resolved[type_name].as_ref()) {
15193 resolve_to_simple[unit_idx] = true;
15194 }
15195 if let Some(existing) = &resolved_type_decomp {
15196 if existing != &unit_decomp {
15197 unit_errors.push(Error::validation(
15198 format!(
15199 "In spec '{}': measure type '{}' has inconsistent unit \
15200 decompositions. Unit '{}' resolved to {:?} but other units \
15201 resolved to {:?}. All units of a measure must measure the same \
15202 physical measure.",
15203 spec_name, type_name, unit.name, unit_decomp, existing
15204 ),
15205 type_source.clone(),
15206 None::<String>,
15207 ));
15208 }
15209 } else {
15210 resolved_type_decomp = Some(unit_decomp);
15211 }
15212
15213 resolved_unit_factors[unit_idx] = Some(derived_factor);
15214 }
15215 Err(err) => unit_errors.push(err),
15216 }
15217 }
15218
15219 if !unit_errors.is_empty() {
15220 errors.extend(unit_errors);
15221 continue;
15222 }
15223
15224 let type_decomp = match resolved_type_decomp {
15225 Some(d) => d,
15226 None => continue,
15227 };
15228
15229 let owned = unwrap_unique_resolved_type(
15230 resolved
15231 .shift_remove(type_name)
15232 .expect("BUG: type_name comes from resolved's own keys"),
15233 );
15234
15235 let updated = owned.map_measure(|units, _| {
15236 let units = MeasureUnits(
15237 units
15238 .0
15239 .into_iter()
15240 .enumerate()
15241 .map(|(idx, u)| {
15242 let u = u.with_decomposition(type_decomp.clone());
15243 let u = match resolved_unit_factors[idx].clone() {
15244 Some(factor) => u.with_factor(factor),
15245 None => u,
15246 };
15247 if resolve_to_simple[idx] {
15248 u.with_derived_measure_factors(Vec::new())
15249 } else {
15250 u
15251 }
15252 })
15253 .collect(),
15254 );
15255 (units, Some(type_decomp.clone()))
15256 });
15257
15258 if let TypeSpecification::Measure { units, .. } = &updated.specifications {
15259 for unit in units.iter() {
15260 if body_already_resolved && !unit_owned_locally(&unit.name) {
15263 continue;
15264 }
15265 let Some(factor) = unit.literal_factor().cloned() else {
15266 continue;
15267 };
15268 lookup.insert(
15269 unit.name.clone(),
15270 (Some(type_name.clone()), type_decomp.clone(), factor),
15271 );
15272 }
15273 }
15274 resolved.insert(type_name.clone(), Arc::new(updated));
15275 }
15276
15277 let resolved = canonicalize_unit_signatures(resolved);
15278
15279 (resolved, errors)
15280}
15281
15282fn finalize_lemma_measure_magnitudes(
15287 lemma_type: LemmaType,
15288 declared_suggestion: Option<&BoundValueKind>,
15289 type_name: &str,
15290) -> Result<LemmaType, String> {
15291 let LemmaType {
15292 name,
15293 mut specifications,
15294 extends,
15295 options,
15296 } = lemma_type;
15297 semantics::finalize_measure_unit_constraint_magnitudes(
15298 &mut specifications,
15299 declared_suggestion.map(|bound| &bound.value),
15300 type_name,
15301 )?;
15302 Ok(LemmaType {
15303 name,
15304 specifications,
15305 extends,
15306 options,
15307 })
15308}
15309
15310fn finalize_measure_magnitudes_in_resolved(
15311 resolved: IndexMap<String, Arc<LemmaType>>,
15312 declared_suggestions: &IndexMap<String, BoundValueKind>,
15313 type_sources: &HashMap<String, Source>,
15314 spec_name: &str,
15315 spec: &LemmaSpec,
15316) -> (IndexMap<String, Arc<LemmaType>>, Vec<Error>) {
15317 resolved.into_iter().fold(
15318 (IndexMap::new(), Vec::new()),
15319 |(mut acc, mut errors), (type_name, arc)| {
15320 let source = type_sources.get(&type_name).cloned().unwrap_or_else(|| {
15321 unreachable!(
15322 "BUG: resolved type '{}' has no corresponding DataTypeDef in spec '{}'",
15323 type_name, spec_name
15324 )
15325 });
15326 let owned = unwrap_unique_resolved_type(arc);
15327 let lemma_type = match finalize_lemma_measure_magnitudes(
15328 owned.clone(),
15329 declared_suggestions.get(&type_name),
15330 &type_name,
15331 ) {
15332 Ok(lt) => lt,
15333 Err(message) => {
15334 errors.push(Error::validation_with_context(
15335 format!(
15336 "Type '{}' has invalid measure unit constraints: {}",
15337 type_name, message
15338 ),
15339 Some(source),
15340 None::<String>,
15341 Some(spec),
15342 None,
15343 ));
15344 owned
15345 }
15346 };
15347 acc.insert(type_name, Arc::new(lemma_type));
15348 (acc, errors)
15349 },
15350 )
15351}
15352
15353fn canonicalize_unit_signatures(types: TypeMap) -> TypeMap {
15361 types
15362 .into_iter()
15363 .map(|(name, arc)| {
15364 (
15365 name,
15366 Arc::new(canonicalize_lemma_unit_signatures(
15367 unwrap_unique_resolved_type(arc),
15368 )),
15369 )
15370 })
15371 .collect()
15372}
15373
15374fn canonicalize_lemma_unit_signatures(lemma_type: LemmaType) -> LemmaType {
15375 lemma_type.map_measure(|units, decomposition| {
15376 let units = units.map(canonicalize_unit_for_measure);
15377 (units, decomposition)
15378 })
15379}
15380
15381fn reserved_calendar_units_in_measure(lemma_type: &LemmaType) -> Vec<String> {
15382 let TypeSpecification::Measure { units, .. } = &lemma_type.specifications else {
15383 return Vec::new();
15384 };
15385 units
15386 .iter()
15387 .filter(|u| calendar_unit_factor(&u.name).is_some())
15388 .map(|u| u.name.clone())
15389 .collect()
15390}
15391
15392fn measure_unit_is_simple(unit: &MeasureUnit) -> bool {
15393 unit.derived_measure_factors.is_empty()
15394 || (unit.derived_measure_factors.len() == 1
15395 && unit.derived_measure_factors[0].0 == unit.name
15396 && unit.derived_measure_factors[0].1 == 1)
15397}
15398
15399fn canonicalize_unit_for_measure(unit: MeasureUnit) -> MeasureUnit {
15400 let factors = if measure_unit_is_simple(&unit) {
15401 vec![(unit.name.clone(), 1)]
15402 } else {
15403 canonicalize_signature(&unit.derived_measure_factors)
15404 };
15405 unit.with_derived_measure_factors(factors)
15406}
15407
15408fn resolve_compound_unit(
15409 spec_name: &str,
15410 declaring_type_name: &str,
15411 unit: &MeasureUnit,
15412 lookup: &UnitDecompLookup,
15413 unit_index: &PlanningUnitIndex,
15414 resolved: &IndexMap<String, Arc<LemmaType>>,
15415 source: Option<&Source>,
15416) -> Result<(BaseMeasureVector, RationalInteger), Error> {
15417 use crate::computation::bigint::BigInt;
15418 use crate::computation::rational::{checked_mul, checked_pow_integer};
15419
15420 let mut result: BaseMeasureVector = BaseMeasureVector::new();
15421 if unit.is_dynamic() {
15422 return Err(Error::validation(
15423 format!(
15424 "In spec '{spec_name}': unit '{}' in measure type '{declaring_type_name}' cannot be used in a compound unit expression because its conversion factor is a reference.",
15425 unit.name
15426 ),
15427 source.cloned(),
15428 None::<String>,
15429 ));
15430 }
15431 let mut derived_factor = unit.require_literal_factor().clone();
15432
15433 for (measure_ref, exponent) in &unit.derived_measure_factors {
15434 let (owning_measure_name, owning_decomp, unit_factor) = if let Some(entry) =
15435 lookup.get(measure_ref.as_str())
15436 {
15437 (entry.0.clone(), entry.1.clone(), entry.2.clone())
15438 } else {
15439 let (bare, owner) = unit_index
15440 .resolve_owner(measure_ref, resolved)
15441 .map_err(|err| {
15442 Error::validation(
15443 format!(
15444 "In spec '{}': unit '{}' in measure type '{}' references '{}': {}. \
15445 Add `uses <spec>` (or declare the owning measure type in this spec) \
15446 so its units are in scope.",
15447 spec_name, unit.name, declaring_type_name, measure_ref, err
15448 ),
15449 source.cloned(),
15450 None::<String>,
15451 )
15452 })?;
15453 let owning_type = owner.lemma_type(resolved);
15454 let TypeSpecification::Measure {
15455 decomposition: Some(decomposition),
15456 units,
15457 ..
15458 } = &owning_type.specifications
15459 else {
15460 return Err(Error::validation(
15461 format!(
15462 "In spec '{}': unit '{}' in measure type '{}' references '{}' which did not resolve to a measure unit",
15463 spec_name, unit.name, declaring_type_name, measure_ref
15464 ),
15465 source.cloned(),
15466 None::<String>,
15467 ));
15468 };
15469 let referenced = units.iter().find(|u| u.name == bare).unwrap_or_else(|| {
15470 panic!(
15471 "BUG: resolved unit '{}' must be declared on type '{}'",
15472 bare,
15473 owning_type.name()
15474 )
15475 });
15476 let Some(factor) = referenced.literal_factor().cloned() else {
15477 return Err(Error::validation(
15478 format!(
15479 "In spec '{spec_name}': unit '{}' in measure type '{declaring_type_name}' cannot use unit '{bare}' in a compound expression because its conversion factor is a reference.",
15480 unit.name
15481 ),
15482 source.cloned(),
15483 None::<String>,
15484 ));
15485 };
15486 (local_owner_name(owner), decomposition.clone(), factor)
15487 };
15488 let owning_decomp = &owning_decomp;
15489 let unit_factor = &unit_factor;
15490
15491 if owning_measure_name.as_deref() == Some(declaring_type_name) {
15492 return Err(Error::validation(
15493 format!(
15494 "In spec '{}': unit '{}' in measure type '{}' references unit '{}' which \
15495 belongs to the same measure type. A measure cannot reference its own units \
15496 in a compound expression.",
15497 spec_name, unit.name, declaring_type_name, measure_ref
15498 ),
15499 source.cloned(),
15500 None::<String>,
15501 ));
15502 }
15503
15504 for (dim, &dim_exp) in owning_decomp {
15505 let combined = dim_exp
15506 .checked_mul(*exponent)
15507 .and_then(|value| accumulate(&mut result, dim, value));
15508 if combined.is_none() {
15509 return Err(unit_exponent_limit_error(
15510 spec_name,
15511 &unit.name,
15512 declaring_type_name,
15513 source,
15514 ));
15515 }
15516 }
15517
15518 let component_contribution =
15519 checked_pow_integer(unit_factor, &BigInt::from_i64(i64::from(*exponent))).map_err(
15520 |error| {
15521 overflow_to_validation_error(
15522 spec_name,
15523 &unit.name,
15524 declaring_type_name,
15525 measure_ref,
15526 error,
15527 source,
15528 )
15529 },
15530 )?;
15531 derived_factor =
15532 checked_mul(&derived_factor, &component_contribution).map_err(|error| {
15533 overflow_to_validation_error(
15534 spec_name,
15535 &unit.name,
15536 declaring_type_name,
15537 measure_ref,
15538 error,
15539 source,
15540 )
15541 })?;
15542 }
15543
15544 Ok((result, derived_factor))
15545}
15546
15547fn overflow_to_validation_error(
15548 spec_name: &str,
15549 unit_name: &str,
15550 declaring_type_name: &str,
15551 measure_ref: &str,
15552 failure: NumericFailure,
15553 source: Option<&Source>,
15554) -> Error {
15555 Error::validation(
15556 format!(
15557 "In spec '{}': unit '{}' in measure type '{}' overflowed while combining '{}': {}",
15558 spec_name, unit_name, declaring_type_name, measure_ref, failure
15559 ),
15560 source.cloned(),
15561 None::<String>,
15562 )
15563}
15564
15565fn unit_exponent_limit_error(
15566 spec_name: &str,
15567 unit_name: &str,
15568 declaring_type_name: &str,
15569 source: Option<&Source>,
15570) -> Error {
15571 Error::validation(
15572 format!(
15573 "In spec '{}': unit '{}' in measure type '{}' has a unit exponent exceeding the maximum of {}",
15574 spec_name,
15575 unit_name,
15576 declaring_type_name,
15577 MAX_UNIT_EXPONENT
15578 ),
15579 source.cloned(),
15580 None::<String>,
15581 )
15582}
15583
15584fn accumulate(
15586 result: &mut BaseMeasureVector,
15587 dim: &semantics::Dimension,
15588 value: i32,
15589) -> Option<()> {
15590 let entry = result.entry(dim.clone()).or_insert(0);
15591 *entry = entry.checked_add(value)?;
15592 if entry.unsigned_abs() > MAX_UNIT_EXPONENT.unsigned_abs() {
15593 return None;
15594 }
15595 if *entry == 0 {
15596 result.remove(dim);
15597 }
15598 Some(())
15599}
15600
15601#[cfg(test)]
15602mod tests {
15603 use super::*;
15604
15605 use crate::parsing::ast::{BooleanValue, Reference, Span, Value};
15606
15607 #[test]
15608 fn nodes_on_cycles_skips_nodes_that_only_depend_on_a_cycle() {
15609 let edges: BTreeMap<&str, Vec<&str>> = BTreeMap::from([
15611 ("a", vec!["b"]),
15612 ("b", vec!["a"]),
15613 ("c", vec!["a"]),
15614 ("d", vec!["b"]),
15615 ("e", vec!["d", "f"]),
15616 ("f", vec!["e"]),
15617 ("g", vec!["g"]),
15618 ]);
15619 let unsorted: Vec<&&str> = edges.keys().collect();
15620 let on_cycles = nodes_on_cycles(&unsorted, |node| edges[node].iter().collect());
15621 assert_eq!(on_cycles, vec![&"a", &"b", &"e", &"f", &"g"]);
15622 }
15623
15624 fn test_source() -> Source {
15625 Source::new(
15626 crate::parsing::source::SourceType::Volatile,
15627 Span {
15628 start: 0,
15629 end: 0,
15630 line: 1,
15631 col: 0,
15632 },
15633 )
15634 }
15635
15636 fn build_graph(
15637 main_spec: &LemmaSpec,
15638 all_specs: &[LemmaSpec],
15639 ) -> Result<Graph<'static>, Vec<Error>> {
15640 use crate::engine::Context;
15641 use crate::planning::discovery;
15642
15643 let mut ctx = Context::new();
15644 let repository = ctx.workspace();
15645 for s in all_specs {
15646 ctx.insert_spec(Arc::clone(&repository), s.clone())?;
15647 }
15648 let effective = EffectiveDate::from_option(main_spec.effective_from().cloned());
15649 let ctx = Box::leak(Box::new(ctx));
15650 let repository = ctx.workspace();
15651 let main_spec_ref = ctx
15652 .spec_set(&repository, main_spec.name.as_str())
15653 .and_then(|ss| ss.get_exact(main_spec.effective_from()))
15654 .expect("main_spec must be in all_specs");
15655 let limits = Box::leak(Box::new(crate::ResourceLimits::default()));
15656 let ordered_dependencies = discovery::discover_dependency_order(
15657 ctx,
15658 &repository,
15659 main_spec_ref,
15660 &effective,
15661 limits,
15662 )
15663 .map_err(|e| match e {
15664 discovery::DependencyDiscoveryError::Cycle(es)
15665 | discovery::DependencyDiscoveryError::Other(es) => es,
15666 })?;
15667 let ordered_dependencies = Box::leak(Box::new(ordered_dependencies));
15668 let mut interners = instance_list::PassInterners::default();
15669 match Graph::build(
15670 ctx,
15671 &repository,
15672 main_spec_ref,
15673 ordered_dependencies,
15674 &effective,
15675 limits,
15676 &mut interners,
15677 ) {
15678 Ok((graph, _)) => Ok(graph),
15679 Err(errors) => Err(errors),
15680 }
15681 }
15682
15683 fn create_test_spec(name: &str) -> LemmaSpec {
15684 LemmaSpec::new(name.to_string())
15685 }
15686
15687 fn create_literal_data(name: &str, value: Value) -> LemmaData {
15688 LemmaData {
15689 reference: Reference {
15690 segments: Vec::new(),
15691 name: name.to_string(),
15692 },
15693 value: ParsedDataValue::Definition {
15694 base: None,
15695 constraints: None,
15696 value: Some(value),
15697
15698 list: None,
15699 },
15700 source_location: test_source(),
15701 }
15702 }
15703
15704 fn create_literal_expr(value: Value) -> ast::Expression {
15705 ast::Expression::leaf(ast::ExpressionKind::Literal(value), Some(test_source()))
15706 }
15707
15708 #[test]
15709 fn dotted_data_definition_rows_are_not_bindings() {
15710 let mut spec = create_test_spec("test");
15712 spec = spec.add_data(create_literal_data("x", Value::Number(1.into())));
15713
15714 spec = spec.add_data(LemmaData {
15715 reference: Reference::from_path(vec!["x".to_string(), "y".to_string()]),
15716 value: ParsedDataValue::Definition {
15717 base: None,
15718 constraints: None,
15719 value: Some(Value::Number(2.into())),
15720
15721 list: None,
15722 },
15723 source_location: test_source(),
15724 });
15725
15726 let result = build_graph(&spec, &[spec.clone()]);
15727 assert!(
15728 result.is_ok(),
15729 "dotted definition rows are ignored by binding collection"
15730 );
15731 }
15732
15733 #[test]
15734 fn programmatic_ast_without_source_location_is_validation_error() {
15735 let mut spec = create_test_spec("test");
15736 spec = spec.add_rule(LemmaRule {
15737 name: "no_location".to_string(),
15738 expression: ast::Expression::leaf(
15739 ast::ExpressionKind::Literal(Value::Number(1.into())),
15740 None,
15741 ),
15742 unless_clauses: Vec::new(),
15743 source_location: test_source(),
15744 });
15745
15746 let result = build_graph(&spec, &[spec.clone()]);
15747 let errors = result.expect_err("missing source_location must be a planning error");
15748 assert!(
15749 errors
15750 .iter()
15751 .any(|e| e.to_string().contains("source location")),
15752 "expected source-location boundary error, got: {errors:?}"
15753 );
15754 }
15755
15756 #[test]
15757 fn programmatic_ast_nested_expression_without_source_location_is_validation_error() {
15758 let mut table = ast::ExprTable::default();
15759 let first = table.push(
15760 ast::ExpressionKind::Literal(Value::Number(2.into())),
15761 Some(test_source()),
15762 );
15763 let operand = table.push(ast::ExpressionKind::Literal(Value::Number(1.into())), None);
15764 let root = table.push(
15765 ast::ExpressionKind::Arithmetic {
15766 first,
15767 steps: vec![ast::ChainStep {
15768 op: ast::ArithmeticComputation::Add,
15769 operand,
15770 source_location: test_source(),
15771 }],
15772 },
15773 Some(test_source()),
15774 );
15775 let outer = ast::Expression::new(table, root);
15776 let mut spec = create_test_spec("test");
15777 spec = spec.add_rule(LemmaRule {
15778 name: "nested_no_location".to_string(),
15779 expression: outer,
15780 unless_clauses: Vec::new(),
15781 source_location: test_source(),
15782 });
15783
15784 let result = build_graph(&spec, &[spec.clone()]);
15785 let errors = result.expect_err("nested missing source_location must be a planning error");
15786 assert!(
15787 errors
15788 .iter()
15789 .any(|e| e.to_string().contains("source location")),
15790 "expected source-location boundary error, got: {errors:?}"
15791 );
15792 }
15793
15794 #[test]
15795 fn should_reject_data_and_rule_name_collision() {
15796 let mut spec = create_test_spec("test");
15801 spec = spec.add_data(create_literal_data("x", Value::Number(1.into())));
15802 spec = spec.add_rule(LemmaRule {
15803 name: "x".to_string(),
15804 expression: create_literal_expr(Value::Number(2.into())),
15805 unless_clauses: Vec::new(),
15806 source_location: test_source(),
15807 });
15808
15809 let result = build_graph(&spec, &[spec.clone()]);
15810 assert!(
15811 result.is_err(),
15812 "Data and rule name collisions should be rejected"
15813 );
15814 }
15815
15816 #[test]
15817 fn test_duplicate_data() {
15818 let mut spec = create_test_spec("test");
15819 spec = spec.add_data(create_literal_data(
15820 "age",
15821 Value::Number(rust_decimal::Decimal::from(25)),
15822 ));
15823 spec = spec.add_data(create_literal_data(
15824 "age",
15825 Value::Number(rust_decimal::Decimal::from(30)),
15826 ));
15827
15828 let result = build_graph(&spec, &[spec.clone()]);
15829 assert!(result.is_err(), "Should detect duplicate data");
15830
15831 let errors = result.unwrap_err();
15832 assert!(errors.iter().any(|e| {
15833 let s = e.to_string();
15834 s.contains("already used") && s.contains("age")
15835 }));
15836 }
15837
15838 #[test]
15839 fn test_duplicate_rule() {
15840 let mut spec = create_test_spec("test");
15841
15842 let rule1 = LemmaRule {
15843 name: "test_rule".to_string(),
15844 expression: create_literal_expr(Value::Boolean(BooleanValue::True)),
15845 unless_clauses: Vec::new(),
15846 source_location: test_source(),
15847 };
15848 let rule2 = LemmaRule {
15849 name: "test_rule".to_string(),
15850 expression: create_literal_expr(Value::Boolean(BooleanValue::False)),
15851 unless_clauses: Vec::new(),
15852 source_location: test_source(),
15853 };
15854
15855 spec = spec.add_rule(rule1);
15856 spec = spec.add_rule(rule2);
15857
15858 let result = build_graph(&spec, &[spec.clone()]);
15859 assert!(result.is_err(), "Should detect duplicate rule");
15860
15861 let errors = result.unwrap_err();
15862 assert!(errors.iter().any(
15863 |e| e.to_string().contains("Duplicate rule") && e.to_string().contains("test_rule")
15864 ));
15865 }
15866
15867 #[test]
15868 fn test_missing_data_reference() {
15869 let mut spec = create_test_spec("test");
15870
15871 let missing_data_expr = ast::Expression::leaf(
15872 ast::ExpressionKind::Reference(Reference {
15873 segments: Vec::new(),
15874 name: "nonexistent".to_string(),
15875 }),
15876 Some(test_source()),
15877 );
15878
15879 let rule = LemmaRule {
15880 name: "test_rule".to_string(),
15881 expression: missing_data_expr,
15882 unless_clauses: Vec::new(),
15883 source_location: test_source(),
15884 };
15885 spec = spec.add_rule(rule);
15886
15887 let result = build_graph(&spec, &[spec.clone()]);
15888 assert!(result.is_err(), "Should detect missing data");
15889
15890 let errors = result.unwrap_err();
15891 assert!(errors
15892 .iter()
15893 .any(|e| e.to_string().contains("Reference 'nonexistent' not found")));
15894 }
15895
15896 fn rule_node_of<'g>(graph: &'g Graph<'static>, rule: &str) -> &'g RuleNode<'static> {
15897 graph
15898 .rules()
15899 .iter()
15900 .find(|(path, _)| path.rule == rule)
15901 .map(|(_, node)| node)
15902 .expect("rule in graph")
15903 }
15904
15905 fn graph_of(source: &str) -> Graph<'static> {
15906 let specs = crate::parse(
15907 source,
15908 crate::parsing::source::SourceType::Volatile,
15909 &crate::ResourceLimits::default(),
15910 )
15911 .expect("fixture parses")
15912 .into_flattened_specs();
15913 let s = specs.iter().find(|d| d.name == "s").expect("spec s");
15914 build_graph(s, &specs).expect("fixture plans")
15915 }
15916
15917 #[test]
15918 fn rule_without_unless_is_its_default_expression() {
15919 let graph = graph_of("spec s\ndata x: number\nrule r: x + 9\n");
15920 let node = rule_node_of(&graph, "r");
15921 node.expression.assert_is_tree();
15922 assert_eq!(node.branches, vec![(None, node.expression.root)]);
15923 assert!(matches!(
15924 node.expression.root_kind(),
15925 ExpressionKind::Arithmetic { .. }
15926 ));
15927 }
15928
15929 #[test]
15930 fn unless_rule_is_one_piecewise_table_in_source_order() {
15931 let graph = graph_of(
15932 "spec s\ndata x: number\nrule r: 0\n unless x > 10 then 100\n unless x > 5 then 50\n",
15933 );
15934 let node = rule_node_of(&graph, "r");
15935 node.expression.assert_is_tree();
15936 let ExpressionKind::Piecewise(arms) = node.expression.root_kind() else {
15937 panic!(
15938 "expected Piecewise root, got {:?}",
15939 node.expression.root_kind()
15940 );
15941 };
15942 let number = |id: ExprId| match node.expression.at(id).kind() {
15943 ExpressionKind::Literal(literal) => match &literal.value {
15944 ValueKind::Number(n) => n.clone(),
15945 other => panic!("expected number, got {other:?}"),
15946 },
15947 other => panic!("expected literal, got {other:?}"),
15948 };
15949 assert_eq!(arms.len(), 3);
15950 assert!(matches!(
15951 node.expression.at(arms[0].0).kind(),
15952 ExpressionKind::Literal(literal) if matches!(literal.value, ValueKind::Boolean(true))
15953 ));
15954 assert_eq!(number(arms[0].1), number(node.branches[0].1));
15955 for (index, expected) in [(1, 100), (2, 50)] {
15956 assert!(matches!(
15957 node.expression.at(arms[index].0).kind(),
15958 ExpressionKind::Comparison(..)
15959 ));
15960 assert_eq!(
15961 (Some(arms[index].0), arms[index].1),
15962 node.branches[index],
15963 "branch ids name the Piecewise arm"
15964 );
15965 assert_eq!(
15966 number(arms[index].1),
15967 crate::computation::rational::rational_new(expected, 1)
15968 );
15969 }
15970 assert_eq!(node.branches[0].0, None);
15971 }
15972
15973 #[test]
15974 fn chained_add_reports_all_missing_references() {
15975 let minimal = r#"spec s
15976data a: 1
15977rule bad: a + missing_one + missing_two
15978"#;
15979 let specs = crate::parse(
15980 minimal,
15981 crate::parsing::source::SourceType::Volatile,
15982 &crate::ResourceLimits::default(),
15983 )
15984 .expect("minimal fixture parses")
15985 .into_flattened_specs();
15986 let s = specs.iter().find(|d| d.name == "s").expect("spec s");
15987 let result = build_graph(s, &specs);
15988 let errors = result.expect_err("chained + with two missing refs must fail planning");
15989 let not_found: Vec<_> = errors
15990 .iter()
15991 .filter(|e| e.to_string().contains("not found"))
15992 .collect();
15993 assert_eq!(
15994 not_found.len(),
15995 2,
15996 "expected two missing-reference errors, got: {}",
15997 errors
15998 .iter()
15999 .map(|e| e.to_string())
16000 .collect::<Vec<_>>()
16001 .join("; ")
16002 );
16003 assert!(
16004 not_found
16005 .iter()
16006 .any(|e| e.to_string().contains("missing_one")),
16007 "expected error for missing_one"
16008 );
16009 assert!(
16010 not_found
16011 .iter()
16012 .any(|e| e.to_string().contains("missing_two")),
16013 "expected error for missing_two"
16014 );
16015 for error in ¬_found {
16016 let source = error
16017 .location()
16018 .expect("missing-reference error must carry source span");
16019 assert_ne!(
16020 source.span.start, source.span.end,
16021 "error span must not be empty"
16022 );
16023 }
16024
16025 let user_shaped = r#"spec s
16026data base_cost: 1
16027data starch_levy: 2
16028data quality_extra: 3
16029rule landed_cost_per_kg:
16030 base_cost + starch_levy + quality_extra + transport_extra_per_kg + amortization_per_kg
16031"#;
16032 let specs = crate::parse(
16033 user_shaped,
16034 crate::parsing::source::SourceType::Volatile,
16035 &crate::ResourceLimits::default(),
16036 )
16037 .expect("user-shaped fixture parses")
16038 .into_flattened_specs();
16039 let s = specs.iter().find(|d| d.name == "s").expect("spec s");
16040 let result = build_graph(s, &specs);
16041 let errors = result.expect_err("landed_cost_per_kg with two typos must fail planning");
16042 let not_found: Vec<_> = errors
16043 .iter()
16044 .filter(|e| e.to_string().contains("not found"))
16045 .collect();
16046 assert_eq!(
16047 not_found.len(),
16048 2,
16049 "expected two missing-reference errors, got: {}",
16050 errors
16051 .iter()
16052 .map(|e| e.to_string())
16053 .collect::<Vec<_>>()
16054 .join("; ")
16055 );
16056 assert!(
16057 not_found
16058 .iter()
16059 .any(|e| e.to_string().contains("transport_extra_per_kg")),
16060 "expected error for transport_extra_per_kg"
16061 );
16062 assert!(
16063 not_found
16064 .iter()
16065 .any(|e| e.to_string().contains("amortization_per_kg")),
16066 "expected error for amortization_per_kg"
16067 );
16068 for error in ¬_found {
16069 let source = error
16070 .location()
16071 .expect("missing-reference error must carry source span");
16072 assert_ne!(
16073 source.span.start, source.span.end,
16074 "error span must not be empty"
16075 );
16076 }
16077 }
16078
16079 #[test]
16080 fn test_missing_spec_reference() {
16081 let mut spec = create_test_spec("test");
16082
16083 let data = LemmaData {
16084 reference: Reference {
16085 segments: Vec::new(),
16086 name: "contract".to_string(),
16087 },
16088 value: ParsedDataValue::import(crate::parsing::ast::SpecRef::same_repository(
16089 "nonexistent",
16090 )),
16091 source_location: test_source(),
16092 };
16093 spec = spec.add_data(data);
16094
16095 let result = build_graph(&spec, &[spec.clone()]);
16096 assert!(result.is_err(), "Should detect missing spec");
16097
16098 let errors = result.unwrap_err();
16099 assert!(
16100 errors.iter().any(|e| e.to_string().contains("nonexistent")),
16101 "Error should mention nonexistent spec: {:?}",
16102 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
16103 );
16104 }
16105
16106 #[test]
16107 fn test_data_reference_conversion() {
16108 let mut spec = create_test_spec("test");
16109 spec = spec.add_data(create_literal_data(
16110 "age",
16111 Value::Number(rust_decimal::Decimal::from(25)),
16112 ));
16113
16114 let age_expr = ast::Expression::leaf(
16115 ast::ExpressionKind::Reference(Reference {
16116 segments: Vec::new(),
16117 name: "age".to_string(),
16118 }),
16119 Some(test_source()),
16120 );
16121
16122 let rule = LemmaRule {
16123 name: "test_rule".to_string(),
16124 expression: age_expr,
16125 unless_clauses: Vec::new(),
16126 source_location: test_source(),
16127 };
16128 spec = spec.add_rule(rule);
16129
16130 let result = build_graph(&spec, &[spec.clone()]);
16131 assert!(result.is_ok(), "Should build graph successfully");
16132
16133 let graph = result.unwrap();
16134 let rule_node = graph.rules().values().next().unwrap();
16135
16136 assert!(matches!(
16137 rule_node.expression.at(rule_node.branches[0].1).kind(),
16138 ExpressionKind::DataPath(_)
16139 ));
16140 }
16141
16142 #[test]
16143 fn test_rule_reference_conversion() {
16144 let mut spec = create_test_spec("test");
16145
16146 let rule1_expr = ast::Expression::leaf(
16147 ast::ExpressionKind::Reference(Reference {
16148 segments: Vec::new(),
16149 name: "age".to_string(),
16150 }),
16151 Some(test_source()),
16152 );
16153
16154 let rule1 = LemmaRule {
16155 name: "rule1".to_string(),
16156 expression: rule1_expr,
16157 unless_clauses: Vec::new(),
16158 source_location: test_source(),
16159 };
16160 spec = spec.add_rule(rule1);
16161
16162 let rule2_expr = ast::Expression::leaf(
16163 ast::ExpressionKind::Reference(Reference {
16164 segments: Vec::new(),
16165 name: "rule1".to_string(),
16166 }),
16167 Some(test_source()),
16168 );
16169
16170 let rule2 = LemmaRule {
16171 name: "rule2".to_string(),
16172 expression: rule2_expr,
16173 unless_clauses: Vec::new(),
16174 source_location: test_source(),
16175 };
16176 spec = spec.add_rule(rule2);
16177
16178 spec = spec.add_data(create_literal_data(
16179 "age",
16180 Value::Number(rust_decimal::Decimal::from(25)),
16181 ));
16182
16183 let result = build_graph(&spec, &[spec.clone()]);
16184 assert!(result.is_ok(), "Should build graph successfully");
16185
16186 let graph = result.unwrap();
16187 let rule2_node = graph
16188 .rules()
16189 .get(&RulePath {
16190 segments: Vec::new(),
16191 rule: "rule2".to_string(),
16192 })
16193 .unwrap();
16194
16195 assert_eq!(rule2_node.depends_on_rules.len(), 1);
16196 assert!(matches!(
16198 rule2_node.expression.at(rule2_node.branches[0].1).kind(),
16199 ExpressionKind::RulePath(_)
16200 ));
16201 }
16202
16203 #[test]
16204 fn test_collect_multiple_errors() {
16205 let mut spec = create_test_spec("test");
16206 spec = spec.add_data(create_literal_data(
16207 "age",
16208 Value::Number(rust_decimal::Decimal::from(25)),
16209 ));
16210 spec = spec.add_data(create_literal_data(
16211 "age",
16212 Value::Number(rust_decimal::Decimal::from(30)),
16213 ));
16214
16215 let missing_data_expr = ast::Expression::leaf(
16216 ast::ExpressionKind::Reference(Reference {
16217 segments: Vec::new(),
16218 name: "nonexistent".to_string(),
16219 }),
16220 Some(test_source()),
16221 );
16222
16223 let rule = LemmaRule {
16224 name: "test_rule".to_string(),
16225 expression: missing_data_expr,
16226 unless_clauses: Vec::new(),
16227 source_location: test_source(),
16228 };
16229 spec = spec.add_rule(rule);
16230
16231 let result = build_graph(&spec, &[spec.clone()]);
16232 assert!(result.is_err(), "Should collect multiple errors");
16233
16234 let errors = result.unwrap_err();
16235 assert!(errors.len() >= 2, "Should have at least 2 errors");
16236 assert!(errors
16237 .iter()
16238 .any(|e| e.to_string().contains("already used")));
16239 assert!(errors
16240 .iter()
16241 .any(|e| e.to_string().contains("Reference 'nonexistent' not found")));
16242 }
16243
16244 #[test]
16245 fn test_type_registration_collects_multiple_errors() {
16246 use crate::parsing::ast::{DataValue, ParentType, PrimitiveKind, SpecRef};
16247
16248 let type_source = Source::new(
16249 crate::parsing::source::SourceType::Volatile,
16250 Span {
16251 start: 0,
16252 end: 0,
16253 line: 1,
16254 col: 0,
16255 },
16256 );
16257 let spec_a = create_test_spec("spec_a")
16258 .with_source_type(crate::parsing::source::SourceType::Volatile)
16259 .add_data(LemmaData {
16260 reference: Reference::local("dep".to_string()),
16261 value: DataValue::import(SpecRef::same_repository("spec_b")),
16262 source_location: type_source.clone(),
16263 })
16264 .add_data(LemmaData {
16265 reference: Reference::local("money".to_string()),
16266 value: DataValue::Definition {
16267 base: Some(ParentType::Primitive {
16268 primitive: PrimitiveKind::Number,
16269 }),
16270 constraints: None,
16271 value: None,
16272
16273 list: None,
16274 },
16275 source_location: type_source.clone(),
16276 })
16277 .add_data(LemmaData {
16278 reference: Reference::local("money".to_string()),
16279 value: DataValue::Definition {
16280 base: Some(ParentType::Primitive {
16281 primitive: PrimitiveKind::Number,
16282 }),
16283 constraints: None,
16284 value: None,
16285
16286 list: None,
16287 },
16288 source_location: type_source,
16289 });
16290
16291 let type_source_b = Source::new(
16292 crate::parsing::source::SourceType::Volatile,
16293 Span {
16294 start: 0,
16295 end: 0,
16296 line: 1,
16297 col: 0,
16298 },
16299 );
16300 let spec_b = create_test_spec("spec_b")
16301 .with_source_type(crate::parsing::source::SourceType::Volatile)
16302 .add_data(LemmaData {
16303 reference: Reference::local("length".to_string()),
16304 value: DataValue::Definition {
16305 base: Some(ParentType::Primitive {
16306 primitive: PrimitiveKind::Number,
16307 }),
16308 constraints: None,
16309 value: None,
16310
16311 list: None,
16312 },
16313 source_location: type_source_b.clone(),
16314 })
16315 .add_data(LemmaData {
16316 reference: Reference::local("length".to_string()),
16317 value: DataValue::Definition {
16318 base: Some(ParentType::Primitive {
16319 primitive: PrimitiveKind::Number,
16320 }),
16321 constraints: None,
16322 value: None,
16323
16324 list: None,
16325 },
16326 source_location: type_source_b,
16327 });
16328
16329 let mut sources = HashMap::new();
16330 sources.insert(
16331 crate::parsing::source::SourceType::Volatile.to_string(),
16332 "spec spec_a\nuses dep: spec_b\ndata money: number\ndata money: number".to_string(),
16333 );
16334 sources.insert(
16335 crate::parsing::source::SourceType::Volatile.to_string(),
16336 "spec spec_b\ndata length: number\ndata length: number".to_string(),
16337 );
16338
16339 let result = build_graph(&spec_a, &[spec_a.clone(), spec_b.clone()]);
16340 assert!(
16341 result.is_err(),
16342 "Should fail with duplicate type/data errors"
16343 );
16344 }
16345
16346 #[test]
16351 fn spec_ref_resolves_to_single_spec_by_name() {
16352 let code = r#"spec myspec
16353data x: 10
16354
16355spec consumer
16356uses m: myspec
16357rule result: m.x"#;
16358 let specs = crate::parse(
16359 code,
16360 crate::parsing::source::SourceType::Volatile,
16361 &crate::ResourceLimits::default(),
16362 )
16363 .unwrap()
16364 .into_flattened_specs();
16365 let consumer = specs.iter().find(|d| d.name == "consumer").unwrap();
16366
16367 let graph = build_graph(consumer, &specs).unwrap();
16368 let Some(DataDefinition::Import { row, .. }) = graph.data.get(&DataPath::local("m".into()))
16369 else {
16370 panic!("`m` is an import slot of the consumer graph");
16371 };
16372 assert_eq!(row.identity.spec, "myspec");
16373 assert!(
16374 matches!(
16375 graph
16376 .template_of_row(row)
16377 .data
16378 .get(&DataPath::local("x".into())),
16379 Some(DataDefinition::Value { .. })
16380 ),
16381 "`m` reads `x` of the one `myspec` template"
16382 );
16383 }
16384
16385 #[test]
16386 fn spec_ref_to_nonexistent_spec_is_error() {
16387 let code = r#"spec myspec
16388data x: 10
16389
16390spec consumer
16391uses m: nonexistent
16392rule result: m.x"#;
16393 let specs = crate::parse(
16394 code,
16395 crate::parsing::source::SourceType::Volatile,
16396 &crate::ResourceLimits::default(),
16397 )
16398 .unwrap()
16399 .into_flattened_specs();
16400 let consumer = specs.iter().find(|d| d.name == "consumer").unwrap();
16401 let result = build_graph(consumer, &specs);
16402 assert!(result.is_err(), "Should fail for non-existent spec");
16403 }
16404
16405 #[test]
16410 fn import_alias_registered_in_graph() {
16411 let code = r#"
16412spec inner
16413data x: number -> suggest 1
16414
16415spec outer
16416uses i: inner
16417rule r: i.x
16418"#;
16419 let specs = crate::parse(
16420 code,
16421 crate::parsing::source::SourceType::Volatile,
16422 &crate::ResourceLimits::default(),
16423 )
16424 .unwrap()
16425 .into_flattened_specs();
16426 let outer = specs.iter().find(|s| s.name == "outer").unwrap();
16427 let graph = build_graph(outer, &specs).expect("uses i: inner must plan");
16428
16429 let alias_path = DataPath {
16430 segments: Vec::new(),
16431 data: "i".to_string(),
16432 };
16433 let Some(DataDefinition::Import { row, .. }) = graph.data().get(&alias_path) else {
16434 panic!(
16435 "alias path 'i' must be DataDefinition::Import, got {:?}",
16436 graph.data().get(&alias_path)
16437 );
16438 };
16439 assert_eq!(row.identity.spec, "inner");
16440 assert!(matches!(row.origin, RowOrigin::Import { ref alias } if alias.as_ref() == "i"));
16441 assert!(
16442 row.bindings.iter().all(Option::is_none),
16443 "`uses i: inner` without `-> with` binds no slot of the import row"
16444 );
16445 assert!(
16446 graph.data().keys().all(|path| path.segments.is_empty()),
16447 "the consumer graph holds its own data only, got {:?}",
16448 graph.data().keys().collect::<Vec<_>>()
16449 );
16450 }
16451
16452 #[test]
16453 fn self_reference_is_error() {
16454 let code = "spec myspec\nuses m: myspec";
16455 let specs = crate::parse(
16456 code,
16457 crate::parsing::source::SourceType::Volatile,
16458 &crate::ResourceLimits::default(),
16459 )
16460 .unwrap()
16461 .into_flattened_specs();
16462 let result = build_graph(&specs[0], &specs);
16463 assert!(result.is_err(), "Self-reference should be an error");
16464 let errors = result.unwrap_err();
16465 let joined: String = errors
16466 .iter()
16467 .map(|e| e.to_string())
16468 .collect::<Vec<_>>()
16469 .join(" ");
16470 assert!(
16471 joined.contains("cannot reference itself") && joined.contains("myspec"),
16472 "Error should name self-reference: {:?}",
16473 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
16474 );
16475 }
16476
16477 mod type_resolution {
16478 use super::super::*;
16479 use crate::computation::rational::rational_new;
16480 use crate::parsing::ast::{
16481 CommandArg, LemmaSpec, ParentType, PrimitiveKind, TypeConstraintCommand,
16482 };
16483 use crate::parsing::parse;
16484 use crate::ResourceLimits;
16485 use rust_decimal::Decimal;
16486 use std::sync::Arc;
16487
16488 fn test_context_and_effective(
16489 specs: &[&LemmaSpec],
16490 ) -> (&'static Context, &'static EffectiveDate) {
16491 use crate::engine::Context;
16492 let mut ctx = Context::new();
16493 let repository = ctx.workspace();
16494 for s in specs {
16495 ctx.insert_spec(Arc::clone(&repository), (*s).clone())
16496 .unwrap();
16497 }
16498 let ctx = Box::leak(Box::new(ctx));
16499 let eff = Box::leak(Box::new(EffectiveDate::Origin));
16500 (ctx, eff)
16501 }
16502
16503 fn context_with_test_spec() -> (&'static Context, &'static LemmaSpec) {
16504 use crate::engine::Context;
16505 let mut ctx = Context::new();
16506 let repository = ctx.workspace();
16507 let spec = LemmaSpec::new("test_spec".to_string());
16508 ctx.insert_spec(Arc::clone(&repository), spec).unwrap();
16509 let ctx: &'static Context = Box::leak(Box::new(ctx));
16510 let repository = ctx.workspace();
16511 let spec = ctx
16512 .spec_set(&repository, "test_spec")
16513 .and_then(|ss| ss.get_exact(None))
16514 .expect("inserted");
16515 (ctx, spec)
16516 }
16517
16518 fn resolver_for_code(code: &str) -> (TypeResolver<'static>, Vec<&'static LemmaSpec>) {
16519 use crate::engine::Context;
16520 let owned = parse(
16521 code,
16522 crate::parsing::source::SourceType::Volatile,
16523 &ResourceLimits::default(),
16524 )
16525 .unwrap()
16526 .into_flattened_specs();
16527 let mut ctx = Context::new();
16528 let repository = ctx.workspace();
16529 for s in owned {
16530 ctx.insert_spec(Arc::clone(&repository), s).unwrap();
16531 }
16532 let ctx = Box::leak(Box::new(ctx));
16533 let repository = ctx.workspace();
16534 let mut spec_refs: Vec<&'static LemmaSpec> = Vec::new();
16535 for ss in ctx.spec_sets_for(&repository) {
16536 for s in ss.iter_specs() {
16537 spec_refs.push(s);
16538 }
16539 }
16540 let mut resolver = TypeResolver::new(ctx, HashMap::new());
16541 for spec in &spec_refs {
16542 resolver.register_all(&repository, spec);
16543 }
16544 (resolver, spec_refs)
16545 }
16546
16547 fn resolver_single_spec(code: &str) -> (TypeResolver<'static>, &LemmaSpec) {
16548 let (resolver, spec_arcs) = resolver_for_code(code);
16549 let spec = spec_arcs.into_iter().next().expect("at least one spec");
16550 (resolver, spec)
16551 }
16552
16553 #[test]
16554 fn test_type_spec_for_primitive_covers_all_variants() {
16555 use crate::parsing::ast::PrimitiveKind;
16556 use crate::planning::semantics::type_spec_for_primitive;
16557
16558 for kind in [
16559 PrimitiveKind::Boolean,
16560 PrimitiveKind::Measure,
16561 PrimitiveKind::MeasureRange,
16562 PrimitiveKind::Number,
16563 PrimitiveKind::NumberRange,
16564 PrimitiveKind::Ratio,
16565 PrimitiveKind::RatioRange,
16566 PrimitiveKind::Text,
16567 PrimitiveKind::Date,
16568 PrimitiveKind::DateRange,
16569 PrimitiveKind::Time,
16570 PrimitiveKind::TimeRange,
16571 ] {
16572 let spec = type_spec_for_primitive(kind);
16573 assert!(
16574 !matches!(
16575 spec,
16576 crate::planning::semantics::TypeSpecification::Undetermined
16577 ),
16578 "type_spec_for_primitive({:?}) returned Undetermined",
16579 kind
16580 );
16581 }
16582 }
16583
16584 #[test]
16585 fn test_register_data_type_def() {
16586 let (ctx, spec) = context_with_test_spec();
16587 let mut resolver = TypeResolver::new(ctx, HashMap::new());
16588 let ftd = DataTypeDef {
16589 parent: ParentType::Primitive {
16590 primitive: PrimitiveKind::Number,
16591 },
16592 constraints: Some(vec![
16593 Constraint::new(
16594 TypeConstraintCommand::Minimum,
16595 vec![CommandArg::Literal(crate::literals::Value::Number(
16596 Decimal::ZERO,
16597 ))],
16598 crate::parsing::source::Source::new(
16599 crate::parsing::source::SourceType::Volatile,
16600 crate::parsing::ast::Span {
16601 start: 0,
16602 end: 0,
16603 line: 1,
16604 col: 0,
16605 },
16606 ),
16607 ),
16608 Constraint::new(
16609 TypeConstraintCommand::Maximum,
16610 vec![CommandArg::Literal(crate::literals::Value::Number(
16611 Decimal::from(150),
16612 ))],
16613 crate::parsing::source::Source::new(
16614 crate::parsing::source::SourceType::Volatile,
16615 crate::parsing::ast::Span {
16616 start: 0,
16617 end: 0,
16618 line: 1,
16619 col: 0,
16620 },
16621 ),
16622 ),
16623 ]),
16624 source: crate::parsing::source::Source::new(
16625 crate::parsing::source::SourceType::Volatile,
16626 crate::parsing::ast::Span {
16627 start: 0,
16628 end: 0,
16629 line: 1,
16630 col: 0,
16631 },
16632 ),
16633 name: "age".to_string(),
16634 bound_literal: None,
16635 };
16636
16637 let result = resolver.register_type(&ctx.workspace(), spec, ftd);
16638 assert!(result.is_ok());
16639 let resolved = resolver
16640 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16641 .unwrap();
16642 assert!(resolved.resolved.contains_key("age"));
16643 }
16644
16645 #[test]
16646 fn test_register_duplicate_type_fails() {
16647 let (ctx, spec) = context_with_test_spec();
16648 let mut resolver = TypeResolver::new(ctx, HashMap::new());
16649 let ftd = DataTypeDef {
16650 parent: ParentType::Primitive {
16651 primitive: PrimitiveKind::Number,
16652 },
16653 constraints: None,
16654 source: crate::parsing::source::Source::new(
16655 crate::parsing::source::SourceType::Volatile,
16656 crate::parsing::ast::Span {
16657 start: 0,
16658 end: 0,
16659 line: 1,
16660 col: 0,
16661 },
16662 ),
16663 name: "money".to_string(),
16664 bound_literal: None,
16665 };
16666 let repository = ctx.workspace();
16667 resolver
16668 .register_type(&repository, spec, ftd.clone())
16669 .unwrap();
16670 let result = resolver.register_type(&repository, spec, ftd);
16671 assert!(result.is_err());
16672 }
16673
16674 #[test]
16675 fn test_resolve_custom_type_from_primitive() {
16676 let (ctx, spec) = context_with_test_spec();
16677 let mut resolver = TypeResolver::new(ctx, HashMap::new());
16678 let ftd = DataTypeDef {
16679 parent: ParentType::Primitive {
16680 primitive: PrimitiveKind::Number,
16681 },
16682 constraints: None,
16683 source: crate::parsing::source::Source::new(
16684 crate::parsing::source::SourceType::Volatile,
16685 crate::parsing::ast::Span {
16686 start: 0,
16687 end: 0,
16688 line: 1,
16689 col: 0,
16690 },
16691 ),
16692 name: "money".to_string(),
16693 bound_literal: None,
16694 };
16695
16696 resolver.register_type(&ctx.workspace(), spec, ftd).unwrap();
16697 let resolved = resolver
16698 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16699 .unwrap();
16700
16701 assert!(resolved.resolved.contains_key("money"));
16702 let money_type = resolved.resolved.get("money").unwrap();
16703 assert_eq!(money_type.name, Some("money".to_string()));
16704 }
16705
16706 #[test]
16707 fn test_child_measure_type_keeps_declared_name_and_child_units() {
16708 let (resolver, spec) = resolver_single_spec(
16709 r#"spec test
16710 data length: measure
16711 -> unit meter: 1
16712 data road_length: length
16713 -> unit kilometer: 1000"#,
16714 );
16715
16716 let resolved_types = resolver
16717 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16718 .unwrap();
16719
16720 let road_length_type = resolved_types.resolved.get("road_length").unwrap();
16721 assert_eq!(road_length_type.name.as_deref(), Some("road_length"));
16722
16723 match &road_length_type.specifications {
16724 TypeSpecification::Measure { units, .. } => {
16725 assert!(units.iter().any(|unit| unit.name == "kilometer"));
16726 }
16727 _ => panic!("Expected Measure type specifications"),
16728 }
16729
16730 let kilometer_owner = resolved_types.unit_index.unique_owner("kilometer").unwrap();
16731 assert_eq!(kilometer_owner.name.as_deref(), Some("road_length"));
16732 assert!(
16733 Arc::ptr_eq(
16734 kilometer_owner,
16735 resolved_types.resolved.get("road_length").unwrap()
16736 ),
16737 "bound kilometer owner must be the resolved road_length Arc"
16738 );
16739 }
16740
16741 #[test]
16742 fn test_type_definition_resolution() {
16743 let (resolver, spec) = resolver_single_spec(
16744 r#"spec test
16745 data dice: number -> minimum 0 -> maximum 6"#,
16746 );
16747
16748 let resolved_types = resolver
16749 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16750 .unwrap();
16751 let dice_type = resolved_types.resolved.get("dice").unwrap();
16752
16753 match &dice_type.specifications {
16754 TypeSpecification::Number {
16755 minimum, maximum, ..
16756 } => {
16757 assert_eq!(minimum, &Some(rational_new(0, 1)));
16758 assert_eq!(maximum, &Some(rational_new(6, 1)));
16759 }
16760 _ => panic!("Expected Number type specifications"),
16761 }
16762 }
16763
16764 #[test]
16765 fn test_type_definition_with_multiple_commands() {
16766 let (resolver, spec) = resolver_single_spec(
16767 r#"spec test
16768 data money: measure -> unit eur: 1.0 -> unit usd: 1.18 -> minimum 0 eur"#,
16769 );
16770
16771 let resolved_types = resolver
16772 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16773 .unwrap();
16774 let money_type = resolved_types.resolved.get("money").unwrap();
16775
16776 match &money_type.specifications {
16777 TypeSpecification::Measure { minimum, units, .. } => {
16778 assert_eq!(minimum.as_ref().map(|(_, unit)| unit.as_str()), Some("eur"));
16779 assert_eq!(units.len(), 2);
16780 assert!(units.iter().any(|u| u.name == "eur"));
16781 assert!(units.iter().any(|u| u.name == "usd"));
16782 }
16783 _ => panic!("Expected Measure type specifications"),
16784 }
16785 }
16786
16787 #[test]
16788 fn test_number_type_with_decimals() {
16789 let (resolver, spec) = resolver_single_spec(
16790 r#"spec test
16791 data price: number -> decimals 2 -> minimum 0"#,
16792 );
16793
16794 let resolved_types = resolver
16795 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16796 .unwrap();
16797 let price_type = resolved_types.resolved.get("price").unwrap();
16798
16799 match &price_type.specifications {
16800 TypeSpecification::Number {
16801 decimals, minimum, ..
16802 } => {
16803 assert_eq!(*decimals, Some(2));
16804 assert_eq!(minimum, &Some(rational_new(0, 1)));
16805 }
16806 _ => panic!("Expected Number type specifications with decimals"),
16807 }
16808 }
16809
16810 #[test]
16811 fn test_number_type_decimals_only() {
16812 let (resolver, spec) = resolver_single_spec(
16813 r#"spec test
16814 data precise_number: number -> decimals 4"#,
16815 );
16816
16817 let resolved_types = resolver
16818 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16819 .unwrap();
16820 let precise_type = resolved_types.resolved.get("precise_number").unwrap();
16821
16822 match &precise_type.specifications {
16823 TypeSpecification::Number { decimals, .. } => {
16824 assert_eq!(*decimals, Some(4));
16825 }
16826 _ => panic!("Expected Number type with decimals 4"),
16827 }
16828 }
16829
16830 #[test]
16831 fn test_measure_type_rejects_decimals() {
16832 let (resolver, spec) = resolver_single_spec(
16833 r#"spec test
16834 data weight: measure -> unit kg: 1 -> decimals 3"#,
16835 );
16836
16837 let errors = resolver
16838 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16839 .expect_err("decimals on a measure must be a planning error");
16840 let messages: Vec<String> = errors.iter().map(|e| e.to_string()).collect();
16841 assert!(
16842 messages.iter().any(|m| m.contains(
16843 "'decimals' is not valid on measure type 'weight': precision depends on the unit. Round in a rule instead, for example 'weight -> round 0.001 kg'."
16844 )),
16845 "got: {messages:?}"
16846 );
16847 }
16848
16849 #[test]
16850 fn test_ratio_type_rejects_decimals() {
16851 let (resolver, spec) = resolver_single_spec(
16852 r#"spec test
16853 data ratio_type: ratio -> decimals 2"#,
16854 );
16855
16856 let errors = resolver
16857 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16858 .expect_err("decimals on a ratio must be a planning error");
16859 let messages: Vec<String> = errors.iter().map(|e| e.to_string()).collect();
16860 assert!(
16861 messages.iter().any(|m| m.contains(
16862 "'decimals' is not valid on ratio type 'ratio_type': precision depends on the unit. Round in a rule instead, for example 'ratio_type -> round 0.01 percent'."
16863 )),
16864 "got: {messages:?}"
16865 );
16866 }
16867
16868 #[test]
16869 fn typedef_default_inherits_through_extension_chain() {
16870 let (resolver, spec) = resolver_single_spec(
16871 r#"spec test
16872 data money: measure -> unit eur: 1 -> suggest 4 eur
16873 data price: money
16874 data final_price: price"#,
16875 );
16876
16877 let resolved = resolver
16878 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16879 .expect("extension chain must resolve");
16880
16881 assert!(
16882 resolved.declared_suggestions.contains_key("money"),
16883 "base typedef default must convert to ValueKind"
16884 );
16885 assert!(
16886 resolved.declared_suggestions.contains_key("final_price"),
16887 "suggestion must inherit through extension chain (money → price → final_price)"
16888 );
16889 }
16890
16891 #[test]
16892 fn test_ratio_type_with_default_command() {
16893 let (resolver, spec) = resolver_single_spec(
16894 r#"spec test
16895 data percentage: ratio -> minimum 0% -> maximum 100% -> suggest 50%"#,
16896 );
16897
16898 let resolved_types = resolver
16899 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
16900 .unwrap();
16901 let percentage_type = resolved_types.resolved.get("percentage").unwrap();
16902
16903 match &percentage_type.specifications {
16904 TypeSpecification::Ratio {
16905 minimum, maximum, ..
16906 } => {
16907 assert_eq!(
16908 *minimum,
16909 Some(rational_new(0, 1)),
16910 "ratio type should have minimum 0"
16911 );
16912 assert_eq!(
16913 *maximum,
16914 Some(rational_new(1, 1)),
16915 "ratio type should have maximum 1"
16916 );
16917 }
16918 _ => panic!("Expected Ratio type with minimum and maximum"),
16919 }
16920
16921 let declared = resolved_types
16922 .declared_suggestions
16923 .get("percentage")
16924 .expect("declared default must be tracked for percentage");
16925 match &declared.value {
16926 ValueKind::Ratio(v) => {
16927 assert_eq!(v, &rational_new(1, 2));
16928 }
16929 other => panic!("expected Ratio declared default, got {:?}", other),
16930 }
16931 }
16932
16933 #[test]
16934 fn test_measure_extension_chain_same_family_units_allowed() {
16935 let (resolver, spec) = resolver_single_spec(
16936 r#"spec test
16937 data money: measure -> unit eur: 1
16938 data money2: money -> unit usd: 1.24"#,
16939 );
16940
16941 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
16942 assert!(
16943 result.is_ok(),
16944 "Measure extension chain should resolve: {:?}",
16945 result.err()
16946 );
16947
16948 let resolved = result.unwrap();
16949 assert!(
16950 resolved.unit_index.unique_owner("eur").is_some(),
16951 "eur should be in unit_index"
16952 );
16953 assert!(
16954 resolved.unit_index.unique_owner("usd").is_some(),
16955 "usd should be in unit_index"
16956 );
16957 let eur_type = resolved.unit_index.unique_owner("eur").unwrap();
16958 let usd_type = resolved.unit_index.unique_owner("usd").unwrap();
16959 assert_eq!(
16960 eur_type.name.as_deref(),
16961 Some("money"),
16962 "declarer money must own inherited eur, not extension"
16963 );
16964 assert_eq!(
16965 usd_type.name.as_deref(),
16966 Some("money2"),
16967 "usd defined on money2 should be owned by money2"
16968 );
16969 assert!(
16970 Arc::ptr_eq(eur_type, resolved.resolved.get("money").unwrap()),
16971 "bound eur owner must be the resolved money Arc"
16972 );
16973 assert!(
16974 Arc::ptr_eq(usd_type, resolved.resolved.get("money2").unwrap()),
16975 "bound usd owner must be the resolved money2 Arc"
16976 );
16977 }
16978
16979 #[test]
16980 fn test_invalid_parent_type_in_named_type_should_error() {
16981 let (resolver, spec) = resolver_single_spec(
16982 r#"spec test
16983 data invalid: nonexistent_type -> minimum 0"#,
16984 );
16985
16986 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
16987 assert!(result.is_err(), "Should reject invalid parent type");
16988
16989 let errs = result.unwrap_err();
16990 assert!(!errs.is_empty(), "expected at least one error");
16991 let error_msg = errs[0].to_string();
16992 assert!(
16993 error_msg.contains("Unknown parent") && error_msg.contains("nonexistent_type"),
16994 "Error should mention unknown type. Got: {}",
16995 error_msg
16996 );
16997 }
16998
16999 #[test]
17000 fn test_invalid_primitive_type_name_should_error() {
17001 let (resolver, spec) = resolver_single_spec(
17002 r#"spec test
17003 data invalid: choice -> option "a""#,
17004 );
17005
17006 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17007 assert!(result.is_err(), "Should reject invalid type base 'choice'");
17008
17009 let errs = result.unwrap_err();
17010 assert!(!errs.is_empty(), "expected at least one error");
17011 let error_msg = errs[0].to_string();
17012 assert!(
17013 error_msg.contains("Unknown parent") && error_msg.contains("choice"),
17014 "Error should mention unknown type 'choice'. Got: {}",
17015 error_msg
17016 );
17017 }
17018
17019 #[test]
17020 fn extension_unit_factor_override_errors() {
17021 let (resolver, spec) = resolver_single_spec(
17022 r#"spec test
17023 data money: measure
17024 -> unit eur: 1
17025 data money2: money
17026 -> unit eur: 1.10"#,
17027 );
17028
17029 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17030 assert!(
17031 result.is_err(),
17032 "extension child must not override inherited unit factor"
17033 );
17034 let error_msg = result
17035 .unwrap_err()
17036 .iter()
17037 .map(ToString::to_string)
17038 .collect::<Vec<_>>()
17039 .join("; ");
17040 assert!(
17041 error_msg.contains("eur"),
17042 "error must name unit eur, got: {error_msg}"
17043 );
17044 assert!(
17045 error_msg.contains("inherited") || error_msg.contains("cannot change"),
17046 "error must reject inherited unit redefinition, got: {error_msg}"
17047 );
17048 }
17049
17050 #[test]
17051 fn inherited_unit_idempotent_redeclare_ok() {
17052 let (resolver, spec) = resolver_single_spec(
17053 r#"spec test
17054 data money: measure
17055 -> unit eur: 1
17056 data money2: money
17057 -> unit eur: 1"#,
17058 );
17059
17060 let resolved = resolver
17061 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17062 .expect("idempotent inherited unit redeclare must resolve");
17063 let money2 = resolved.resolved.get("money2").expect("money2");
17064 match &money2.specifications {
17065 TypeSpecification::Measure { units, .. } => {
17066 let eur = units.iter().find(|u| u.name == "eur").expect("eur");
17067 assert_eq!(
17068 eur.require_literal_factor().try_to_decimal().unwrap(),
17069 Decimal::ONE,
17070 "eur factor must remain 1"
17071 );
17072 }
17073 other => panic!("expected Measure, got {other:?}"),
17074 }
17075 }
17076
17077 #[test]
17078 fn extension_additive_unit_registers_in_unit_index() {
17079 let (resolver, spec) = resolver_single_spec(
17080 r#"spec test
17081 data money: measure
17082 -> unit eur: 1
17083 data money2: money
17084 -> unit usd: 1.24"#,
17085 );
17086
17087 let resolved = resolver
17088 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17089 .expect("additive extension unit must resolve");
17090 let money = resolved
17091 .resolved
17092 .get("money")
17093 .expect("money resolved")
17094 .clone();
17095 let money2 = resolved
17096 .resolved
17097 .get("money2")
17098 .expect("money2 resolved")
17099 .clone();
17100 assert!(
17101 resolved.unit_index.unique_owner("eur").is_some(),
17102 "eur must be in unit_index"
17103 );
17104 assert!(
17105 resolved.unit_index.unique_owner("usd").is_some(),
17106 "usd must be in unit_index"
17107 );
17108 let eur_owner = resolved.unit_index.unique_owner("eur").expect("eur owner");
17109 let usd_owner = resolved.unit_index.unique_owner("usd").expect("usd owner");
17110 assert_eq!(
17111 eur_owner.name.as_deref(),
17112 Some("money"),
17113 "declarer money must own eur, not extension money2"
17114 );
17115 assert_eq!(
17116 usd_owner.name.as_deref(),
17117 Some("money2"),
17118 "money2 must own new unit usd"
17119 );
17120 assert_eq!(eur_owner.as_ref(), money.as_ref());
17121 assert_eq!(usd_owner.as_ref(), money2.as_ref());
17122 }
17123
17124 #[test]
17125 fn find_unique_reports_multiple_families_with_same_decomposition() {
17126 let code = r#"spec units
17127 data money: measure
17128 -> unit eur: 1
17129 -> unit usd: 0.86
17130 data mass: measure
17131 -> unit kg: 1
17132 data price_eur_per_kg: measure
17133 -> unit eur_per_kg: eur/kg
17134 data price_usd_per_kg: measure
17135 -> unit usd_per_kg: usd/kg
17136 "#;
17137 let (resolver, spec_arcs) = resolver_for_code(code);
17138 let units_arc = spec_arcs
17139 .iter()
17140 .find(|spec| spec.name == "units")
17141 .cloned()
17142 .expect("units spec");
17143 let (context, _) = test_context_and_effective(&spec_arcs);
17144 let repository = context.workspace();
17145 let resolved = resolver
17146 .resolve_and_validate(units_arc, &EffectiveDate::Origin, &ResolvedTypesMap::new())
17147 .expect("units resolves");
17148 let eur_decomp = resolved
17149 .unit_index
17150 .unique_owner("eur_per_kg")
17151 .expect("eur_per_kg")
17152 .measure_type_decomposition()
17153 .expect("eur_per_kg decomposition")
17154 .clone();
17155 let map = vec![(repository, units_arc, resolved)];
17156
17157 let unique = find_unique_measure_type_in_unit_index(
17158 &find_types_by_spec(&map, units_arc)
17159 .expect("units in map")
17160 .unit_index,
17161 &eur_decomp,
17162 );
17163 match unique {
17164 DecompositionMatch::Multiple(families) => {
17165 assert_eq!(families.len(), 2);
17166 assert!(families.contains(&"price_eur_per_kg".to_string()));
17167 assert!(families.contains(&"price_usd_per_kg".to_string()));
17168 }
17169 other => panic!("expected Multiple families, got {other:?}"),
17170 }
17171 }
17172
17173 #[test]
17174 fn unit_index_maps_family_root_not_binding_alias() {
17175 let code = r#"spec units
17176 data money: measure
17177 -> unit eur: 1
17178 data mass: measure
17179 -> unit kg: 1
17180 data price_per_weight: measure
17181 -> unit eur_per_kg: eur/kg
17182
17183 spec consumer
17184 uses u: units
17185 data starch_levy_per_kg: u.price_per_weight
17186 -> suggest 0.1 eur_per_kg
17187 data amortization_per_kg: u.price_per_weight
17188 -> suggest 0.2 eur_per_kg
17189 "#;
17190 let (resolver, spec_arcs) = resolver_for_code(code);
17191 let units_arc = spec_arcs
17192 .iter()
17193 .find(|spec| spec.name == "units")
17194 .cloned()
17195 .expect("units spec");
17196 let consumer_arc = spec_arcs
17197 .iter()
17198 .find(|spec| spec.name == "consumer")
17199 .cloned()
17200 .expect("consumer spec");
17201 let (context, _) = test_context_and_effective(&spec_arcs);
17202 let repository = context.workspace();
17203
17204 let mut already_resolved = ResolvedTypesMap::new();
17205 let units_types = resolver
17206 .resolve_and_validate(units_arc, &EffectiveDate::Origin, &already_resolved)
17207 .expect("units spec resolves");
17208 let units_price_per_weight = units_types
17209 .resolved
17210 .get("price_per_weight")
17211 .expect("units defines price_per_weight")
17212 .clone();
17213 already_resolved.push((Arc::clone(&repository), units_arc, units_types));
17214
17215 let consumer_types = resolver
17216 .resolve_and_validate(consumer_arc, &EffectiveDate::Origin, &already_resolved)
17217 .expect("consumer spec resolves");
17218
17219 let eur_per_kg_owner = consumer_types
17220 .unit_index
17221 .unique_owner("eur_per_kg")
17222 .expect("eur_per_kg in unit_index");
17223 assert_eq!(
17224 eur_per_kg_owner.name.as_deref(),
17225 Some("price_per_weight"),
17226 "unit must belong to family root, not binding alias row name"
17227 );
17228 assert_eq!(
17229 eur_per_kg_owner.measure_family_name(),
17230 Some("price_per_weight")
17231 );
17232 assert_eq!(eur_per_kg_owner.as_ref(), units_price_per_weight.as_ref());
17233
17234 let starch_alias = consumer_types
17235 .resolved
17236 .get("starch_levy_per_kg")
17237 .expect("alias row in resolved");
17238 assert!(
17239 !std::ptr::eq(eur_per_kg_owner.as_ref(), starch_alias.as_ref()),
17240 "unit_index must not store binding alias arc"
17241 );
17242 }
17243
17244 #[test]
17245 fn same_named_local_root_does_not_hide_imported_units() {
17246 let code = r#"spec std_units
17247 data mass: measure
17248 -> unit kilogram: 1
17249 -> unit kilograms: 1
17250 -> unit gram: 0.001
17251
17252 spec s
17253 uses u: std_units
17254 data mass: measure
17255 -> unit kg: 1
17256 -> unit tonne: 1000
17257 rule smoke: true
17258 "#;
17259 let (resolver, spec_arcs) = resolver_for_code(code);
17260 let std_units_arc = spec_arcs
17261 .iter()
17262 .find(|spec| spec.name == "std_units")
17263 .cloned()
17264 .expect("std_units spec");
17265 let consumer_arc = spec_arcs
17266 .iter()
17267 .find(|spec| spec.name == "s")
17268 .cloned()
17269 .expect("consumer spec");
17270 let (context, _) = test_context_and_effective(&spec_arcs);
17271 let repository = context.workspace();
17272
17273 let mut already_resolved = ResolvedTypesMap::new();
17274 let std_units_types = resolver
17275 .resolve_and_validate(std_units_arc, &EffectiveDate::Origin, &already_resolved)
17276 .expect("std_units resolves");
17277 already_resolved.push((Arc::clone(&repository), std_units_arc, std_units_types));
17278
17279 let consumer_types = resolver
17280 .resolve_and_validate(consumer_arc, &EffectiveDate::Origin, &already_resolved)
17281 .expect("consumer resolves");
17282
17283 let local_mass = consumer_types
17284 .resolved
17285 .get("mass")
17286 .expect("consumer defines mass")
17287 .clone();
17288
17289 assert!(
17290 consumer_types.unit_index.unique_owner("kg").is_some(),
17291 "local kg must be indexed"
17292 );
17293 assert!(
17294 consumer_types.unit_index.unique_owner("tonne").is_some(),
17295 "local tonne must be indexed"
17296 );
17297 for imported_unit in ["kilogram", "kilograms", "gram"] {
17298 let owner = consumer_types
17299 .unit_index
17300 .unique_owner(imported_unit)
17301 .unwrap_or_else(|| panic!("imported {imported_unit} must stay in scope"));
17302 assert_eq!(
17303 owner.measure_family().map(|family| family.spec.as_str()),
17304 Some("std_units"),
17305 "{imported_unit} must belong to std_units.mass, not the local mass"
17306 );
17307 }
17308
17309 let kg_owner = consumer_types
17310 .unit_index
17311 .unique_owner("kg")
17312 .expect("kg owner");
17313 let tonne_owner = consumer_types
17314 .unit_index
17315 .unique_owner("tonne")
17316 .expect("tonne owner");
17317 assert_eq!(kg_owner.as_ref(), local_mass.as_ref());
17318 assert_eq!(tonne_owner.as_ref(), local_mass.as_ref());
17319 }
17320
17321 #[test]
17322 fn same_family_same_unit_same_factor_is_idempotent() {
17323 let code = r#"spec units_a
17324 data duration: measure
17325 -> unit hour: 1
17326
17327 spec consumer
17328 uses a: units_a
17329 uses b: units_a
17330 rule smoke: true
17331 "#;
17332 let (resolver, spec_arcs) = resolver_for_code(code);
17333 let units_a_arc = spec_arcs
17334 .iter()
17335 .find(|spec| spec.name == "units_a")
17336 .cloned()
17337 .expect("units_a spec");
17338 let consumer_arc = spec_arcs
17339 .iter()
17340 .find(|spec| spec.name == "consumer")
17341 .cloned()
17342 .expect("consumer spec");
17343 let (context, _) = test_context_and_effective(&spec_arcs);
17344 let repository = context.workspace();
17345
17346 let mut already_resolved = ResolvedTypesMap::new();
17347 let units_a_types = resolver
17348 .resolve_and_validate(units_a_arc, &EffectiveDate::Origin, &already_resolved)
17349 .expect("units_a resolves");
17350 already_resolved.push((Arc::clone(&repository), units_a_arc, units_a_types));
17351
17352 let consumer_types = resolver
17353 .resolve_and_validate(consumer_arc, &EffectiveDate::Origin, &already_resolved)
17354 .expect("double import of same duration must be idempotent");
17355
17356 assert_eq!(
17357 consumer_types.unit_index.owners_for("hour").len(),
17358 2,
17359 "each import path keeps its hour owner"
17360 );
17361 consumer_types
17362 .unit_index
17363 .resolve("hour")
17364 .expect("bare hour collapses across identical import paths");
17365 }
17366
17367 #[test]
17368 fn different_version_families_keep_separate_hour_owners() {
17369 let code = r#"spec units 2024-01-01
17372 data duration: measure
17373 -> unit hour: 1
17374
17375 spec units 2025-01-01
17376 data duration: measure
17377 -> unit hour: 60
17378
17379 spec consumer
17380 uses a: units 2024-01-01
17381 uses b: units 2025-01-01
17382 rule smoke: true
17383 "#;
17384 let (resolver, spec_arcs) = resolver_for_code(code);
17385 let consumer_arc = spec_arcs
17386 .iter()
17387 .find(|spec| spec.name == "consumer")
17388 .cloned()
17389 .expect("consumer spec");
17390 let (context, _) = test_context_and_effective(&spec_arcs);
17391 let repository = context.workspace();
17392
17393 let mut already_resolved = ResolvedTypesMap::new();
17394 for units_arc in spec_arcs.iter().filter(|spec| spec.name == "units") {
17395 let units_types = resolver
17396 .resolve_and_validate(units_arc, &units_arc.effective_from, &already_resolved)
17397 .expect("units version resolves");
17398 already_resolved.push((Arc::clone(&repository), units_arc, units_types));
17399 }
17400
17401 let consumer_types = resolver
17402 .resolve_and_validate(consumer_arc, &EffectiveDate::Origin, &already_resolved)
17403 .expect("different version families resolve without merge conflict");
17404 assert_eq!(
17405 consumer_types.unit_index.owners_for("hour").len(),
17406 2,
17407 "each version keeps its own hour owner"
17408 );
17409 }
17410
17411 #[test]
17412 fn test_spec_level_same_unit_names_keep_every_declarer() {
17413 let (resolver, spec) = resolver_single_spec(
17414 r#"spec test
17415 data money_a: measure
17416 -> unit eur: 1.00
17417 -> unit usd: 0.84
17418
17419 data money_b: measure
17420 -> unit eur: 1.00
17421 -> unit usd: 1.20
17422
17423 data length_a: measure
17424 -> unit meter: 1.0
17425
17426 data length_b: measure
17427 -> unit meter: 1.0"#,
17428 );
17429
17430 let resolved = resolver
17431 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17432 .expect("independent declarers of one unit name must load");
17433 assert_eq!(
17434 resolved.unit_index.owners_for("eur").len(),
17435 2,
17436 "every declarer of 'eur' stays in scope"
17437 );
17438 assert!(
17439 resolved.unit_index.resolve("eur").is_err(),
17440 "bare 'eur' with two declarers must be qualified"
17441 );
17442 assert_eq!(
17443 resolved.unit_index.owners_for("usd").len(),
17444 2,
17445 "every declarer of 'usd' stays in scope"
17446 );
17447 assert!(
17448 resolved.unit_index.resolve("usd").is_err(),
17449 "bare 'usd' with two declarers must be qualified"
17450 );
17451 assert_eq!(
17452 resolved.unit_index.owners_for("meter").len(),
17453 2,
17454 "every declarer of 'meter' stays in scope"
17455 );
17456 assert!(
17457 resolved.unit_index.resolve("meter").is_err(),
17458 "bare 'meter' with two declarers must be qualified"
17459 );
17460 let (_, owner) = resolved
17461 .unit_index
17462 .resolve("money_b.usd")
17463 .expect("qualified unit must resolve");
17464 assert_eq!(owner.name(), "money_b");
17465 }
17466
17467 #[test]
17468 fn test_measure_and_ratio_sharing_a_unit_name_keep_both_declarers() {
17469 let (resolver, spec) = resolver_single_spec(
17470 r#"spec test
17471 data q: measure
17472 -> unit foo: 1
17473
17474 data r: ratio
17475 -> unit foo: 100"#,
17476 );
17477
17478 let resolved = resolver
17479 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17480 .expect("independent declarers of one unit name must load");
17481 assert_eq!(
17482 resolved.unit_index.owners_for("foo").len(),
17483 2,
17484 "every declarer of 'foo' stays in scope"
17485 );
17486 assert!(
17487 resolved.unit_index.resolve("foo").is_err(),
17488 "bare 'foo' with two declarers must be qualified"
17489 );
17490 let (_, owner) = resolved
17491 .unit_index
17492 .resolve("r.foo")
17493 .expect("qualified unit must resolve");
17494 assert_eq!(owner.name(), "r");
17495 }
17496
17497 #[test]
17498 fn test_same_ratio_unit_across_types_keeps_both_declarers() {
17499 let (resolver, spec) = resolver_single_spec(
17500 r#"spec test
17501 data spread_a: ratio
17502 -> unit basis_points: 10000
17503
17504 data spread_b: ratio
17505 -> unit basis_points: 10000"#,
17506 );
17507
17508 let resolved = resolver
17509 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17510 .expect("independent declarers of one unit name must load");
17511 assert_eq!(
17512 resolved.unit_index.owners_for("basis_points").len(),
17513 2,
17514 "every declarer of 'basis_points' stays in scope"
17515 );
17516 assert!(
17517 resolved.unit_index.resolve("basis_points").is_err(),
17518 "bare 'basis_points' with two declarers must be qualified"
17519 );
17520 let (_, owner) = resolved
17521 .unit_index
17522 .resolve("spread_a.basis_points")
17523 .expect("qualified unit must resolve");
17524 assert_eq!(owner.name(), "spread_a");
17525 }
17526
17527 #[test]
17528 fn test_different_ratio_unit_factor_across_types_keeps_both_declarers() {
17529 let (resolver, spec) = resolver_single_spec(
17530 r#"spec test
17531 data spread_a: ratio
17532 -> unit basis_points: 10000
17533
17534 data spread_b: ratio
17535 -> unit basis_points: 5000"#,
17536 );
17537
17538 let resolved = resolver
17539 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17540 .expect("independent declarers of one unit name must load");
17541 assert_eq!(
17542 resolved.unit_index.owners_for("basis_points").len(),
17543 2,
17544 "every declarer of 'basis_points' stays in scope"
17545 );
17546 assert!(
17547 resolved.unit_index.resolve("basis_points").is_err(),
17548 "bare 'basis_points' with two declarers must be qualified"
17549 );
17550 let (_, owner) = resolved
17551 .unit_index
17552 .resolve("spread_b.basis_points")
17553 .expect("qualified unit must resolve");
17554 assert_eq!(owner.name(), "spread_b");
17555 }
17556
17557 #[test]
17558 fn test_multiple_builtin_ratio_types_share_percent_in_unit_index() {
17559 let (resolver, spec) = resolver_single_spec(
17560 r#"spec targets
17561 data standard_margin_pct: ratio
17562 -> minimum 0%
17563 -> suggest 15%
17564
17565 data default_credit_insurance_pct: ratio
17566 -> suggest 1.5%"#,
17567 );
17568
17569 resolver
17570 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17571 .expect("multiple ratio types with built-in percent must not conflict");
17572 }
17573
17574 #[test]
17575 fn test_three_ratio_types_share_builtin_and_custom_unit() {
17576 let (resolver, spec) = resolver_single_spec(
17577 r#"spec test
17578 data margin: ratio -> suggest 10%
17579 data fee: ratio
17580 -> unit tenths: 10
17581 data tax: ratio -> suggest 1%"#,
17582 );
17583
17584 resolver
17585 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17586 .expect("builtin percent/permille plus custom tenths on second type must load");
17587 }
17588
17589 #[test]
17590 fn test_ratio_unit_index_allows_builtin_after_two_named_types() {
17591 let (resolver, spec) = resolver_single_spec(
17592 r#"spec test
17593 data first_ratio: ratio -> suggest 5%
17594 data second_ratio: ratio -> suggest 10%"#,
17595 );
17596
17597 let resolved = resolver
17598 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17599 .expect("two ratio types with only builtin units");
17600
17601 let (_, percent) = resolved
17602 .unit_index
17603 .resolve("percent")
17604 .expect("bare percent binds PrimitiveRatio");
17605 assert_eq!(percent.name(), "ratio");
17606 let (_, permille) = resolved
17607 .unit_index
17608 .resolve("permille")
17609 .expect("bare permille binds PrimitiveRatio");
17610 assert_eq!(permille.name(), "ratio");
17611 }
17612
17613 #[test]
17614 fn test_number_type_cannot_have_units() {
17615 let (resolver, spec) = resolver_single_spec(
17616 r#"spec test
17617 data price: number
17618 -> unit eur: 1.00"#,
17619 );
17620
17621 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17622 assert!(result.is_err(), "Number types must reject unit commands");
17623
17624 let errs = result.unwrap_err();
17625 assert!(!errs.is_empty(), "expected at least one error");
17626 let error_msg = errs[0].to_string();
17627 assert!(
17628 error_msg.contains("unit") && error_msg.contains("number"),
17629 "Error should mention units are invalid on number. Got: {}",
17630 error_msg
17631 );
17632 }
17633
17634 #[test]
17635 fn test_extending_type_inherits_units() {
17636 let (resolver, spec) = resolver_single_spec(
17637 r#"spec test
17638 data money: measure
17639 -> unit eur: 1.00
17640 -> unit usd: 0.84
17641
17642 data my_money: money
17643 -> unit gbp: 1.30"#,
17644 );
17645
17646 let resolved = resolver
17647 .resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new())
17648 .unwrap();
17649 let my_money_type = resolved.resolved.get("my_money").unwrap();
17650
17651 match &my_money_type.specifications {
17652 TypeSpecification::Measure { units, .. } => {
17653 assert_eq!(units.len(), 3);
17654 assert!(units.iter().any(|u| u.name == "eur"));
17655 assert!(units.iter().any(|u| u.name == "usd"));
17656 assert!(units.iter().any(|u| u.name == "gbp"));
17657 }
17658 other => panic!("Expected Measure type specifications, got {:?}", other),
17659 }
17660 }
17661
17662 #[test]
17663 fn binding_unit_factor_override_errors() {
17664 let (resolver, spec) = resolver_single_spec(
17665 r#"spec test
17666 data source_measure: measure
17667 -> unit usd: 1.00
17668 data z: source_measure
17669 -> unit usd: 0.84"#,
17670 );
17671
17672 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17673 assert!(
17674 result.is_err(),
17675 "binding row must not override inherited unit factor"
17676 );
17677 let error_msg = result
17678 .unwrap_err()
17679 .iter()
17680 .map(ToString::to_string)
17681 .collect::<Vec<_>>()
17682 .join("; ");
17683 assert!(
17684 error_msg.contains("usd"),
17685 "error must name unit usd, got: {error_msg}"
17686 );
17687 }
17688
17689 #[test]
17690 fn ratio_extension_unit_factor_override_errors() {
17691 let (resolver, spec) = resolver_single_spec(
17692 r#"spec test
17693 data parent: ratio
17694 -> unit basis: 1
17695 data child: parent
17696 -> unit basis: 100"#,
17697 );
17698
17699 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17700 assert!(
17701 result.is_err(),
17702 "ratio extension must not override inherited unit factor"
17703 );
17704 let error_msg = result
17705 .unwrap_err()
17706 .iter()
17707 .map(ToString::to_string)
17708 .collect::<Vec<_>>()
17709 .join("; ");
17710 assert!(
17711 error_msg.contains("basis"),
17712 "error must name unit basis, got: {error_msg}"
17713 );
17714 }
17715
17716 #[test]
17717 fn test_duplicate_unit_name_in_same_type_is_error() {
17718 let (resolver, spec) = resolver_single_spec(
17719 r#"spec test
17720 data money: measure
17721 -> unit eur: 1.00
17722 -> unit eur: 1.19"#,
17723 );
17724
17725 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17726 assert!(result.is_err(), "duplicate unit name must be rejected");
17727 let errs = result.unwrap_err();
17728 assert!(!errs.is_empty());
17729 let msg = errs[0].to_string();
17730 assert!(
17731 msg.contains("eur"),
17732 "error must name the duplicate unit; got: {msg}"
17733 );
17734 }
17735
17736 #[test]
17737 fn test_duplicate_unit_name_compound_is_error() {
17738 let (resolver, spec) = resolver_single_spec(
17739 r#"spec test
17740 data currency: measure
17741 -> unit usd: 1.00
17742 -> unit eur: 0.92
17743
17744 data time_period: measure
17745 -> unit year: 1
17746
17747 data run_rate: measure
17748 -> unit arr: usd/year
17749 -> unit arr: eur/year"#,
17750 );
17751
17752 let result = resolver.resolve_and_validate(spec, &EffectiveDate::Origin, &Vec::new());
17753 assert!(
17754 result.is_err(),
17755 "duplicate compound unit name must be rejected"
17756 );
17757 let errs = result.unwrap_err();
17758 assert!(!errs.is_empty());
17759 let msg = errs[0].to_string();
17760 assert!(
17761 msg.contains("arr"),
17762 "error must name the duplicate unit; got: {msg}"
17763 );
17764 }
17765 }
17766
17767 mod validation {
17768 use super::super::*;
17769 use crate::computation::rational::rational_new;
17770 use crate::literals::UnitScale;
17771 use crate::parsing::ast::{CommandArg, TypeConstraintCommand};
17772 use crate::planning::semantics::TypeSpecification;
17773 use rust_decimal::Decimal;
17774
17775 fn test_source() -> Source {
17776 Source::new(
17777 crate::parsing::source::SourceType::Volatile,
17778 crate::parsing::ast::Span {
17779 start: 0,
17780 end: 0,
17781 line: 1,
17782 col: 0,
17783 },
17784 )
17785 }
17786
17787 fn apply(
17788 mut specs: TypeSpecification,
17789 command: TypeConstraintCommand,
17790 args: &[CommandArg],
17791 ) -> TypeSpecification {
17792 let mut suggestion: Option<RawSuggestion> = None;
17793 specs
17794 .apply_constraint(
17795 ConstraintNames {
17796 type_name: "test",
17797 data_name: "test",
17798 },
17799 command,
17800 args,
17801 &mut suggestion,
17802 &mut None,
17803 None,
17804 )
17805 .unwrap();
17806 specs
17807 }
17808
17809 fn number_arg(n: i64) -> CommandArg {
17810 CommandArg::Literal(crate::literals::Value::Number(Decimal::from(n)))
17811 }
17812
17813 fn date_arg(s: &str) -> CommandArg {
17814 let dt = s.parse::<crate::literals::DateTimeValue>().expect("date");
17815 CommandArg::Literal(crate::literals::Value::Date(dt))
17816 }
17817
17818 fn time_arg(s: &str) -> CommandArg {
17819 let t = s.parse::<crate::literals::TimeValue>().expect("time");
17820 CommandArg::Literal(crate::literals::Value::Time(t))
17821 }
17822
17823 #[test]
17824 fn validate_number_minimum_greater_than_maximum() {
17825 let mut specs = TypeSpecification::number();
17826 specs = apply(specs, TypeConstraintCommand::Minimum, &[number_arg(100)]);
17827 specs = apply(specs, TypeConstraintCommand::Maximum, &[number_arg(50)]);
17828
17829 let src = test_source();
17830 let errors = validate_type_specifications(
17831 &specs,
17832 None,
17833 "suggestion",
17834 "test",
17835 &src,
17836 None,
17837 &UnitIndex::new(),
17838 );
17839 assert_eq!(errors.len(), 1);
17840 assert!(errors[0]
17841 .to_string()
17842 .contains("minimum 100 is greater than maximum 50"));
17843 }
17844
17845 #[test]
17846 fn validate_number_default_below_minimum() {
17847 let specs = TypeSpecification::Number {
17848 minimum: Some(rational_new(10, 1)),
17849 maximum: None,
17850 decimals: None,
17851 help: String::new(),
17852 };
17853 let default = BoundValueKind::unbound(ValueKind::Number(rational_new(5, 1)));
17854
17855 let src = test_source();
17856 let errors = validate_type_specifications(
17857 &specs,
17858 Some(&default),
17859 "suggestion",
17860 "test",
17861 &src,
17862 None,
17863 &UnitIndex::new(),
17864 );
17865 assert_eq!(errors.len(), 1);
17866 assert!(errors[0]
17867 .to_string()
17868 .contains("suggestion value 5 is less than minimum 10"));
17869 }
17870
17871 #[test]
17872 fn validate_number_default_above_maximum() {
17873 let specs = TypeSpecification::Number {
17874 minimum: None,
17875 maximum: Some(rational_new(100, 1)),
17876 decimals: None,
17877 help: String::new(),
17878 };
17879 let default = BoundValueKind::unbound(ValueKind::Number(rational_new(150, 1)));
17880
17881 let src = test_source();
17882 let errors = validate_type_specifications(
17883 &specs,
17884 Some(&default),
17885 "suggestion",
17886 "test",
17887 &src,
17888 None,
17889 &UnitIndex::new(),
17890 );
17891 assert_eq!(errors.len(), 1);
17892 assert!(errors[0]
17893 .to_string()
17894 .contains("suggestion value 150 is greater than maximum 100"));
17895 }
17896
17897 #[test]
17898 fn validate_number_default_valid() {
17899 let specs = TypeSpecification::Number {
17900 minimum: Some(rational_new(0, 1)),
17901 maximum: Some(rational_new(100, 1)),
17902 decimals: None,
17903 help: String::new(),
17904 };
17905 let default = BoundValueKind::unbound(ValueKind::Number(rational_new(50, 1)));
17906
17907 let src = test_source();
17908 let errors = validate_type_specifications(
17909 &specs,
17910 Some(&default),
17911 "suggestion",
17912 "test",
17913 &src,
17914 None,
17915 &UnitIndex::new(),
17916 );
17917 assert!(errors.is_empty());
17918 }
17919
17920 #[test]
17921 fn text_minimum_command_is_rejected() {
17922 let mut specs = TypeSpecification::text();
17923 let res = specs.apply_constraint(
17924 ConstraintNames {
17925 type_name: "test",
17926 data_name: "test",
17927 },
17928 TypeConstraintCommand::Minimum,
17929 &[number_arg(5)],
17930 &mut None,
17931 &mut None,
17932 None,
17933 );
17934 assert!(res.is_err());
17935 assert!(res
17936 .unwrap_err()
17937 .contains("Invalid command 'minimum' for text type"));
17938 }
17939
17940 #[test]
17941 fn text_maximum_command_is_rejected() {
17942 let mut specs = TypeSpecification::text();
17943 let res = specs.apply_constraint(
17944 ConstraintNames {
17945 type_name: "test",
17946 data_name: "test",
17947 },
17948 TypeConstraintCommand::Maximum,
17949 &[number_arg(5)],
17950 &mut None,
17951 &mut None,
17952 None,
17953 );
17954 assert!(res.is_err());
17955 assert!(res
17956 .unwrap_err()
17957 .contains("Invalid command 'maximum' for text type"));
17958 }
17959
17960 #[test]
17961 fn validate_text_default_not_in_options() {
17962 let mut lemma_type = LemmaType::primitive(TypeSpecification::Text {
17963 length: None,
17964 help: String::new(),
17965 });
17966 lemma_type.options.values = vec![
17967 crate::planning::semantics::OptionValue {
17968 value: crate::planning::semantics::BoundValueKind::unbound(ValueKind::Text(
17969 Arc::from("red"),
17970 )),
17971 },
17972 crate::planning::semantics::OptionValue {
17973 value: crate::planning::semantics::BoundValueKind::unbound(ValueKind::Text(
17974 Arc::from("blue"),
17975 )),
17976 },
17977 ];
17978 let default = BoundValueKind::unbound(ValueKind::Text(Arc::from("green")));
17979
17980 let src = test_source();
17981 let errors = validate_type_options(
17982 &lemma_type,
17983 Some(&default),
17984 "suggestion",
17985 "test",
17986 &src,
17987 None,
17988 &UnitIndex::new(),
17989 );
17990 assert_eq!(errors.len(), 1);
17991 assert!(
17992 errors[0].to_string().contains("green")
17993 && errors[0].to_string().contains("allowed options")
17994 );
17995 }
17996
17997 #[test]
17998 fn validate_ratio_minimum_greater_than_maximum() {
17999 let specs = TypeSpecification::Ratio {
18000 minimum: Some(rational_new(2, 1)),
18001 maximum: Some(rational_new(1, 1)),
18002 units: crate::planning::semantics::RatioUnits::new(),
18003 unit: None,
18004 help: String::new(),
18005 };
18006
18007 let src = test_source();
18008 let errors = validate_type_specifications(
18009 &specs,
18010 None,
18011 "suggestion",
18012 "test",
18013 &src,
18014 None,
18015 &UnitIndex::new(),
18016 );
18017 assert_eq!(errors.len(), 1);
18018 assert!(errors[0]
18019 .to_string()
18020 .contains("minimum 2 is greater than maximum 1"));
18021 }
18022
18023 #[test]
18024 fn validate_date_minimum_after_maximum() {
18025 let mut specs = TypeSpecification::date();
18026 specs = apply(
18027 specs,
18028 TypeConstraintCommand::Minimum,
18029 &[date_arg("2024-12-31")],
18030 );
18031 specs = apply(
18032 specs,
18033 TypeConstraintCommand::Maximum,
18034 &[date_arg("2024-01-01")],
18035 );
18036
18037 let src = test_source();
18038 let errors = validate_type_specifications(
18039 &specs,
18040 None,
18041 "suggestion",
18042 "test",
18043 &src,
18044 None,
18045 &UnitIndex::new(),
18046 );
18047 assert_eq!(errors.len(), 1);
18048 assert!(
18049 errors[0].to_string().contains("minimum")
18050 && errors[0].to_string().contains("is after maximum")
18051 );
18052 }
18053
18054 #[test]
18055 fn validate_date_valid_range() {
18056 let mut specs = TypeSpecification::date();
18057 specs = apply(
18058 specs,
18059 TypeConstraintCommand::Minimum,
18060 &[date_arg("2024-01-01")],
18061 );
18062 specs = apply(
18063 specs,
18064 TypeConstraintCommand::Maximum,
18065 &[date_arg("2024-12-31")],
18066 );
18067
18068 let src = test_source();
18069 let errors = validate_type_specifications(
18070 &specs,
18071 None,
18072 "suggestion",
18073 "test",
18074 &src,
18075 None,
18076 &UnitIndex::new(),
18077 );
18078 assert!(errors.is_empty());
18079 }
18080
18081 #[test]
18082 fn validate_time_minimum_after_maximum() {
18083 let mut specs = TypeSpecification::time();
18084 specs = apply(
18085 specs,
18086 TypeConstraintCommand::Minimum,
18087 &[time_arg("23:00:00")],
18088 );
18089 specs = apply(
18090 specs,
18091 TypeConstraintCommand::Maximum,
18092 &[time_arg("10:00:00")],
18093 );
18094
18095 let src = test_source();
18096 let errors = validate_type_specifications(
18097 &specs,
18098 None,
18099 "suggestion",
18100 "test",
18101 &src,
18102 None,
18103 &UnitIndex::new(),
18104 );
18105 assert_eq!(errors.len(), 1);
18106 assert!(
18107 errors[0].to_string().contains("minimum")
18108 && errors[0].to_string().contains("is after maximum")
18109 );
18110 }
18111
18112 #[test]
18113 fn large_magnitude_minimum_does_not_fail_type_validation() {
18114 use crate::computation::rational::rational_one;
18115 use crate::literals::MeasureUnits;
18116 use crate::planning::semantics::TypeSpecification;
18117
18118 let too_large = magnitude_beyond_decimal_max();
18119 assert_eq!(
18120 too_large.try_to_decimal().unwrap_err(),
18121 crate::computation::rational::NumericFailure::Overflow,
18122 );
18123
18124 let spec = TypeSpecification::Measure {
18125 minimum: Some((too_large, "eur".to_string())),
18126 maximum: None,
18127 units: MeasureUnits(vec![crate::literals::MeasureUnit {
18128 name: "eur".to_string(),
18129 scale: UnitScale::Literal(rational_one()),
18130 derived_measure_factors: Default::default(),
18131 decomposition: Default::default(),
18132 minimum: None,
18133 maximum: None,
18134 suggestion_magnitude: None,
18135 }]),
18136 traits: vec![],
18137 decomposition: None,
18138 unit: None,
18139 help: String::new(),
18140 };
18141
18142 let src = test_source();
18143 let errors = validate_type_specifications(
18144 &spec,
18145 None,
18146 "suggestion",
18147 "money",
18148 &src,
18149 None,
18150 &UnitIndex::new(),
18151 );
18152 assert!(
18153 errors.is_empty(),
18154 "internal Q bounds must not fail type validation; API decimal rounding: {:?}",
18155 errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
18156 );
18157 }
18158
18159 #[test]
18160 fn large_magnitude_maximum_does_not_fail_type_validation() {
18161 use crate::computation::rational::rational_one;
18162 use crate::literals::MeasureUnits;
18163 use crate::planning::semantics::TypeSpecification;
18164
18165 let too_large = magnitude_beyond_decimal_max();
18166 assert_eq!(
18167 too_large.try_to_decimal().unwrap_err(),
18168 crate::computation::rational::NumericFailure::Overflow,
18169 );
18170
18171 let spec = TypeSpecification::Measure {
18172 minimum: None,
18173 maximum: Some((too_large, "eur".to_string())),
18174 units: MeasureUnits(vec![crate::literals::MeasureUnit {
18175 name: "eur".to_string(),
18176 scale: UnitScale::Literal(rational_one()),
18177 derived_measure_factors: Default::default(),
18178 decomposition: Default::default(),
18179 minimum: None,
18180 maximum: None,
18181 suggestion_magnitude: None,
18182 }]),
18183 traits: vec![],
18184 decomposition: None,
18185 unit: None,
18186 help: String::new(),
18187 };
18188
18189 let src = test_source();
18190 let errors = validate_type_specifications(
18191 &spec,
18192 None,
18193 "suggestion",
18194 "money",
18195 &src,
18196 None,
18197 &UnitIndex::new(),
18198 );
18199 assert!(errors.is_empty(), "got: {:?}", errors);
18200 }
18201
18202 #[test]
18203 fn large_magnitude_default_does_not_fail_type_validation() {
18204 use crate::computation::rational::rational_one;
18205 use crate::literals::MeasureUnits;
18206 use crate::planning::semantics::TypeSpecification;
18207
18208 let too_large = magnitude_beyond_decimal_max();
18209 assert_eq!(
18210 too_large.try_to_decimal().unwrap_err(),
18211 crate::computation::rational::NumericFailure::Overflow,
18212 );
18213
18214 let spec = TypeSpecification::Measure {
18215 minimum: None,
18216 maximum: None,
18217 units: MeasureUnits(vec![crate::literals::MeasureUnit {
18218 name: "eur".to_string(),
18219 scale: UnitScale::Literal(rational_one()),
18220 derived_measure_factors: Default::default(),
18221 decomposition: Default::default(),
18222 minimum: None,
18223 maximum: None,
18224 suggestion_magnitude: Some(too_large),
18225 }]),
18226 traits: vec![],
18227 decomposition: None,
18228 unit: None,
18229 help: String::new(),
18230 };
18231
18232 let src = test_source();
18233 let errors = validate_type_specifications(
18234 &spec,
18235 None,
18236 "suggestion",
18237 "money",
18238 &src,
18239 None,
18240 &UnitIndex::new(),
18241 );
18242 assert!(errors.is_empty(), "got: {:?}", errors);
18243 }
18244
18245 fn magnitude_beyond_decimal_max() -> crate::computation::rational::RationalInteger {
18246 use crate::computation::rational::{decimal_to_rational, rational_new, try_mul};
18247 use rust_decimal::Decimal;
18248 let max = Decimal::MAX.normalize();
18249 let max_rational = decimal_to_rational(max).expect("BUG: Decimal::MAX must lift to Q");
18250 try_mul(&max_rational, &rational_new(2, 1))
18251 .expect("BUG: test rational multiply must succeed")
18252 }
18253
18254 #[test]
18255 fn empty_measure_signature_unit_must_not_coerce_as_unknown_empty_string() {
18256 use crate::computation::rational::rational_new;
18257 use crate::literals::MeasureUnits;
18258 use crate::planning::semantics::{
18259 LemmaType, TypeSpecification, TypedLiteral, ValueKind,
18260 };
18261
18262 let schema_type = Arc::new(LemmaType {
18263 name: Some("money".to_string()),
18264 extends: TypeExtends::Primitive,
18265 specifications: TypeSpecification::Measure {
18266 minimum: None,
18267 maximum: None,
18268 units: MeasureUnits(vec![crate::literals::MeasureUnit {
18269 name: "eur".to_string(),
18270 scale: UnitScale::Literal(rational_new(1, 1)),
18271 derived_measure_factors: Default::default(),
18272 decomposition: Default::default(),
18273 minimum: None,
18274 maximum: None,
18275 suggestion_magnitude: None,
18276 }]),
18277 traits: vec![],
18278 decomposition: Default::default(),
18279 unit: None,
18280 help: String::new(),
18281 },
18282 options: TypeOptions::default(),
18283 });
18284
18285 let literal = TypedLiteral {
18286 value: ValueKind::Measure(rational_new(1, 1)),
18287 lemma_type: Arc::clone(&schema_type),
18288 };
18289
18290 let result = Graph::coerce_literal_to_schema_type(&literal, &schema_type);
18291
18292 assert!(
18295 result.is_ok(),
18296 "measure with matching schema type must coerce, got: {result:?}"
18297 );
18298 }
18299
18300 #[test]
18301 fn refresh_named_range_specs_missing_element_must_not_leave_unconverted_measure() {
18302 use crate::engine::Context;
18303 use crate::parsing::ast::{LemmaSpec, ParentType, Span};
18304 use crate::parsing::source::{Source, SourceType};
18305 use crate::planning::semantics::tests::primitive_measure_arc;
18306
18307 let mut ctx = Context::new();
18308 let repository = ctx.workspace();
18309 ctx.insert_spec(Arc::clone(&repository), LemmaSpec::new("t".to_string()))
18310 .unwrap();
18311 let ctx: &'static Context = Box::leak(Box::new(ctx));
18312 let mut resolver = TypeResolver::new(ctx, HashMap::new());
18313 let spec = ctx
18314 .spec_set(&ctx.workspace(), "t")
18315 .and_then(|ss| ss.get_exact(None))
18316 .expect("inserted");
18317 assert!(resolver.register_all(&ctx.workspace(), spec).is_empty());
18318 let source = Source::new(
18319 SourceType::Volatile,
18320 Span {
18321 start: 0,
18322 end: 0,
18323 line: 1,
18324 col: 0,
18325 },
18326 );
18327 let mut data_defs = HashMap::new();
18328 data_defs.insert(
18329 "band".to_string(),
18330 DataTypeDef {
18331 parent: ParentType::Ranged {
18332 inner: Box::new(ParentType::custom("ghost")),
18333 },
18334 constraints: None,
18335 source: source.clone(),
18336 name: "band".to_string(),
18337 bound_literal: None,
18338 },
18339 );
18340 let mut resolved_types = ResolvedSpecTypes::default();
18341 resolved_types
18342 .resolved
18343 .insert("band".to_string(), primitive_measure_arc().clone());
18344 let already_resolved = ResolvedTypesMap::new();
18345
18346 let errors = refresh_named_range_specs(
18347 &resolver,
18348 spec,
18349 &data_defs,
18350 &mut resolved_types,
18351 &already_resolved,
18352 &EffectiveDate::Origin,
18353 );
18354
18355 assert!(
18356 !errors.is_empty(),
18357 "planning must error when ranged inner type is missing"
18358 );
18359 assert!(
18360 errors[0]
18361 .to_string()
18362 .contains("Unknown parent 'ghost' for data definition"),
18363 "got: {}",
18364 errors[0]
18365 );
18366 }
18367
18368 fn apply_n_text_options(option_count: usize) -> LemmaType {
18369 let spec = LemmaSpec::new("wide_options".to_string());
18370 let source = test_source();
18371 let constraints: Vec<Constraint> = (0..option_count)
18372 .map(|i| {
18373 Constraint::new(
18374 TypeConstraintCommand::Option,
18375 vec![CommandArg::Literal(crate::literals::Value::Text(
18376 i.to_string(),
18377 ))],
18378 source.clone(),
18379 )
18380 })
18381 .collect();
18382 let mut suggestion = None;
18383 let mut value = None;
18384 let mut lemma_type = LemmaType::primitive(TypeSpecification::text());
18385 apply_constraints_to_spec(
18386 &spec,
18387 "code",
18388 "code",
18389 &mut lemma_type,
18390 &constraints,
18391 &source,
18392 ConstraintSuggestionSink {
18393 suggestion: &mut suggestion,
18394 value: &mut value,
18395 resolve_unit: None,
18396 },
18397 )
18398 .expect("options must apply");
18399 lemma_type
18400 }
18401
18402 #[test]
18403 fn apply_constraints_many_text_options_preserve_count() {
18404 for count in [16_usize, 64] {
18405 assert_eq!(apply_n_text_options(count).options.values.len(), count);
18406 }
18407 }
18408 }
18409
18410 mod decomposition_promotion {
18411 use super::super::*;
18412 use crate::engine::Context;
18413 use crate::literals::UnitScale;
18414 use crate::parsing::parse;
18415 use crate::planning::typing::range_span_type;
18416 use crate::ResourceLimits;
18417 use std::sync::Arc;
18418
18419 fn insert_parsed_repos(ctx: &mut Context, code: &str) {
18420 let result = parse(
18421 code,
18422 crate::parsing::source::SourceType::Volatile,
18423 &ResourceLimits::default(),
18424 )
18425 .expect("parse");
18426 for (repo_arc, specs) in result.repositories {
18427 for spec in specs {
18428 ctx.insert_spec(Arc::clone(&repo_arc), spec)
18429 .expect("insert spec");
18430 }
18431 }
18432 }
18433
18434 fn repo_by_name(ctx: &Context, name: &str) -> Arc<LemmaRepository> {
18435 ctx.repositories()
18436 .keys()
18437 .find(|r| r.name.as_deref() == Some(name))
18438 .cloned()
18439 .expect("repository must exist after insert")
18440 }
18441
18442 fn resolve_specs_in_order<'a>(
18443 resolver: &TypeResolver<'a>,
18444 repo: &Arc<LemmaRepository>,
18445 specs: &[&'a LemmaSpec],
18446 local_types: &mut ResolvedTypesMap<'a>,
18447 ) {
18448 for spec in specs {
18449 let types = resolver
18450 .resolve_and_validate(spec, &EffectiveDate::Origin, local_types)
18451 .unwrap_or_else(|e| panic!("resolve {} failed: {:?}", spec.name, e));
18452 local_types.push((Arc::clone(repo), *spec, types));
18453 }
18454 }
18455
18456 fn worker_torque_fixture() -> (
18457 ResolvedTypesMap<'static>,
18458 &'static LemmaSpec,
18459 BaseMeasureVector,
18460 Arc<LemmaType>,
18461 ) {
18462 let mut ctx = Context::new();
18463 insert_parsed_repos(&mut ctx, crate::stdlib::UNITS_LEMMA);
18464 let alpha_code = r#"repo alpha
18465 spec units
18466 data force: measure
18467 -> unit newton: 1
18468 data length: measure
18469 -> unit meter: 1
18470 data torque: measure
18471 -> unit nm: newton*meter
18472
18473 spec worker
18474 uses u: alpha units
18475 data f: 3 newton
18476 data d: 4 meter
18477 rule t: f * d
18478 "#;
18479 insert_parsed_repos(&mut ctx, alpha_code);
18480 let ctx = Box::leak(Box::new(ctx));
18481 let alpha_repo = repo_by_name(ctx, "alpha");
18482 let worker = ctx
18483 .spec_set(&alpha_repo, "worker")
18484 .and_then(|ss| ss.get_exact(None))
18485 .expect("worker spec");
18486 let units = ctx
18487 .spec_set(&alpha_repo, "units")
18488 .and_then(|ss| ss.get_exact(None))
18489 .expect("alpha units spec");
18490 let lemma_repo = repo_by_name(ctx, crate::engine::EMBEDDED_STDLIB_REPOSITORY);
18491 let lemma_units = ctx
18492 .spec_set(&lemma_repo, "units")
18493 .expect("lemma repo")
18494 .get_exact(None)
18495 .expect("lemma units spec");
18496
18497 let mut resolver = TypeResolver::new(ctx, HashMap::new());
18498 for spec in [units, worker] {
18499 resolver
18500 .register_all(&alpha_repo, spec)
18501 .into_iter()
18502 .for_each(|e| panic!("register alpha: {e:?}"));
18503 }
18504 resolver
18505 .register_all(&lemma_repo, lemma_units)
18506 .into_iter()
18507 .for_each(|e| panic!("register lemma units: {e:?}"));
18508
18509 let mut local_types = ResolvedTypesMap::new();
18510 resolve_specs_in_order(
18511 &resolver,
18512 &lemma_repo,
18513 std::slice::from_ref(&lemma_units),
18514 &mut local_types,
18515 );
18516 resolve_specs_in_order(
18517 &resolver,
18518 &alpha_repo,
18519 std::slice::from_ref(&units),
18520 &mut local_types,
18521 );
18522 resolve_specs_in_order(
18523 &resolver,
18524 &alpha_repo,
18525 std::slice::from_ref(&worker),
18526 &mut local_types,
18527 );
18528
18529 let alpha_torque = local_types
18530 .iter()
18531 .find(|(_, s, _)| discovery::same_loaded_spec(s, units))
18532 .and_then(|(_, _, t)| t.resolved.get("torque"))
18533 .expect("alpha torque type")
18534 .clone();
18535 let decomp = alpha_torque
18536 .measure_type_decomposition()
18537 .expect("torque decomposition")
18538 .clone();
18539
18540 (local_types, worker, decomp, alpha_torque)
18541 }
18542
18543 #[test]
18544 fn unique_decomposition_carries_matching_arc() {
18545 let (map, worker_spec, decomp, alpha_torque) = worker_torque_fixture();
18546 let unique = find_unique_measure_type_in_unit_index(
18547 &find_types_by_spec(&map, worker_spec)
18548 .expect("worker in map")
18549 .unit_index,
18550 &decomp,
18551 );
18552 match unique {
18553 DecompositionMatch::Unique(arc) => {
18554 assert_eq!(*arc, *alpha_torque);
18555 assert_eq!(arc.name.as_deref(), Some("torque"));
18556 }
18557 other => panic!("expected Unique(Arc) torque match, got {other:?}"),
18558 }
18559 }
18560
18561 #[test]
18562 fn find_unique_ignores_polluted_resolved_map() {
18563 let (mut map, worker_spec, decomp, alpha_torque) = worker_torque_fixture();
18564 map.iter_mut()
18565 .find(|(_, s, _)| discovery::same_loaded_spec(s, worker_spec))
18566 .expect("worker must be in resolved map")
18567 .2
18568 .resolved = IndexMap::new();
18569
18570 let unique = find_unique_measure_type_in_unit_index(
18571 &find_types_by_spec(&map, worker_spec)
18572 .expect("worker in map")
18573 .unit_index,
18574 &decomp,
18575 );
18576 match unique {
18577 DecompositionMatch::Unique(arc) => {
18578 assert_eq!(*arc, *alpha_torque);
18579 assert_eq!(arc.name.as_deref(), Some("torque"));
18580 }
18581 other => panic!("expected Unique(alpha torque) via unit_index, got {other:?}"),
18582 }
18583 }
18584
18585 fn weight_measure_type() -> Arc<LemmaType> {
18586 use crate::computation::rational::{decimal_to_rational, rational_one};
18587 use rust_decimal::Decimal;
18588 Arc::new(LemmaType::new(
18589 "weight".to_string(),
18590 TypeSpecification::Measure {
18591 minimum: None,
18592 maximum: None,
18593 units: MeasureUnits(vec![
18594 MeasureUnit {
18595 name: "gram".to_string(),
18596 scale: UnitScale::Literal(rational_one()),
18597 derived_measure_factors: Vec::new(),
18598 decomposition: Default::default(),
18599 minimum: None,
18600 maximum: None,
18601 suggestion_magnitude: None,
18602 },
18603 MeasureUnit {
18604 name: "kilogram".to_string(),
18605 scale: UnitScale::Literal(
18606 decimal_to_rational(Decimal::from(1000))
18607 .expect("BUG: kilogram factor must be rational"),
18608 ),
18609 derived_measure_factors: Vec::new(),
18610 decomposition: Default::default(),
18611 minimum: None,
18612 maximum: None,
18613 suggestion_magnitude: None,
18614 },
18615 ]),
18616 traits: Vec::new(),
18617 decomposition: None,
18618 unit: None,
18619 help: String::new(),
18620 },
18621 TypeExtends::custom_local(
18622 "measure".to_string(),
18623 crate::planning::semantics::tests::test_type_family("weight"),
18624 ),
18625 ))
18626 }
18627
18628 fn weight_measure_range_type() -> Arc<LemmaType> {
18629 let weight = weight_measure_type();
18630 Arc::new(LemmaType {
18631 name: weight.name.clone(),
18632 specifications: weight
18633 .specifications
18634 .range_from_element()
18635 .expect("BUG: weight measure defines MeasureRange"),
18636 extends: weight.extends.clone(),
18637 options: TypeOptions::default(),
18638 })
18639 }
18640
18641 fn duration_like_measure_type() -> Arc<LemmaType> {
18642 Arc::new(LemmaType::anonymous_for_decomposition(
18643 duration_decomposition(),
18644 ))
18645 }
18646
18647 fn calendar_like_measure_type() -> Arc<LemmaType> {
18648 use crate::computation::rational::rational_one;
18649 use crate::planning::semantics::MeasureTrait;
18650 Arc::new(LemmaType::new(
18651 "calendar".to_string(),
18652 TypeSpecification::Measure {
18653 minimum: None,
18654 maximum: None,
18655 units: MeasureUnits(vec![MeasureUnit {
18656 name: "month".to_string(),
18657 scale: UnitScale::Literal(rational_one()),
18658 derived_measure_factors: Vec::new(),
18659 decomposition: calendar_decomposition(),
18660 minimum: None,
18661 maximum: None,
18662 suggestion_magnitude: None,
18663 }]),
18664 traits: vec![MeasureTrait::Calendar],
18665 decomposition: Some(calendar_decomposition()),
18666 unit: None,
18667 help: String::new(),
18668 },
18669 TypeExtends::Primitive,
18670 ))
18671 }
18672
18673 #[test]
18674 fn range_span_type_date_range_is_duration_span_not_date_range() {
18675 let date_range = Arc::new(LemmaType::primitive(TypeSpecification::date_range()));
18676 let span = range_span_type(&date_range);
18677 assert!(span.is_duration_like_measure());
18678 assert!(!span.is_date_range());
18679 assert!(!span.is_date());
18680 }
18681
18682 #[test]
18683 fn range_span_type_time_range_is_duration_span() {
18684 let time_range = Arc::new(LemmaType::primitive(TypeSpecification::time_range()));
18685 let span = range_span_type(&time_range);
18686 assert!(span.is_duration_like_measure());
18687 assert!(!span.is_time_range());
18688 assert!(!span.is_time());
18689 }
18690
18691 #[test]
18692 fn range_span_type_number_range_is_number() {
18693 let number_range = Arc::new(LemmaType::primitive(TypeSpecification::number_range()));
18694 let span = range_span_type(&number_range);
18695 assert!(span.is_number());
18696 }
18697
18698 #[test]
18699 fn range_span_type_measure_range_preserves_name_and_extends() {
18700 let weight_range = weight_measure_range_type();
18701 let span = range_span_type(&weight_range);
18702 assert!(span.is_measure());
18703 assert!(!span.is_measure_range());
18704 assert_eq!(span.name.as_deref(), Some("weight"));
18705 assert_eq!(span.extends, weight_range.extends);
18706 assert!(span.same_measure_family(weight_measure_type().as_ref()));
18707 }
18708
18709 #[test]
18710 fn range_span_type_anonymous_measure_range_preserves_units() {
18711 use crate::computation::rational::{decimal_to_rational, rational_one};
18712 use rust_decimal::Decimal;
18713 let units = MeasureUnits(vec![
18714 MeasureUnit {
18715 name: "gram".to_string(),
18716 scale: UnitScale::Literal(rational_one()),
18717 derived_measure_factors: Vec::new(),
18718 decomposition: Default::default(),
18719 minimum: None,
18720 maximum: None,
18721 suggestion_magnitude: None,
18722 },
18723 MeasureUnit {
18724 name: "kilogram".to_string(),
18725 scale: UnitScale::Literal(
18726 decimal_to_rational(Decimal::from(1000))
18727 .expect("BUG: kilogram factor must be rational"),
18728 ),
18729 derived_measure_factors: Vec::new(),
18730 decomposition: Default::default(),
18731 minimum: None,
18732 maximum: None,
18733 suggestion_magnitude: None,
18734 },
18735 ]);
18736 let measure_range = Arc::new(LemmaType::without_name(
18737 TypeSpecification::MeasureRange {
18738 lower: None,
18739 upper: None,
18740 minimum: None,
18741 maximum: None,
18742 units: units.clone(),
18743 decomposition: None,
18744 help: String::new(),
18745 },
18746 TypeExtends::Primitive,
18747 ));
18748 let span = range_span_type(&measure_range);
18749 assert!(span.is_measure());
18750 assert!(span.is_anonymous_measure());
18751 match &span.specifications {
18752 TypeSpecification::Measure {
18753 units: span_units, ..
18754 } => {
18755 assert_eq!(span_units, &units);
18756 }
18757 other => panic!("expected Measure span, got {other:?}"),
18758 }
18759 }
18760
18761 #[test]
18762 fn range_span_type_ratio_range_is_ratio() {
18763 let ratio_range = Arc::new(LemmaType::primitive(TypeSpecification::ratio_range()));
18764 let span = range_span_type(&ratio_range);
18765 assert!(span.is_ratio());
18766 }
18767
18768 #[test]
18769 fn range_span_type_non_range_is_undetermined() {
18770 let boolean = Arc::new(LemmaType::primitive(TypeSpecification::boolean()));
18771 let span = range_span_type(&boolean);
18772 assert!(span.is_undetermined());
18773 }
18774
18775 #[test]
18776 fn arithmetic_measure_range_plus_measure_yields_named_measure_span() {
18777 let weight_range = weight_measure_range_type();
18778 let gram = weight_measure_type();
18779 let unit_index = UnitIndex::new();
18780 let signature_index = crate::computation::arithmetic::SignatureIndex::new();
18781 let named = IndexMap::new();
18782 let scope = MeasureScope {
18783 unit_index: &unit_index,
18784 signature_index: &signature_index,
18785 named_types: &named,
18786 };
18787 let result = compute_arithmetic_result_type(
18788 weight_range,
18789 &ArithmeticComputation::Add,
18790 gram,
18791 &scope,
18792 );
18793 assert!(result.is_measure());
18794 assert!(!result.is_measure_range());
18795 assert_eq!(result.name.as_deref(), Some("weight"));
18796 assert!(result.same_measure_family(weight_measure_type().as_ref()));
18797 }
18798
18799 #[test]
18800 fn arithmetic_measure_range_minus_measure_yields_named_measure_span() {
18801 let weight_range = weight_measure_range_type();
18802 let gram = weight_measure_type();
18803 let unit_index = UnitIndex::new();
18804 let signature_index = crate::computation::arithmetic::SignatureIndex::new();
18805 let named = IndexMap::new();
18806 let scope = MeasureScope {
18807 unit_index: &unit_index,
18808 signature_index: &signature_index,
18809 named_types: &named,
18810 };
18811 let result = compute_arithmetic_result_type(
18812 weight_range,
18813 &ArithmeticComputation::Subtract,
18814 gram,
18815 &scope,
18816 );
18817 assert!(result.is_measure());
18818 assert!(!result.is_measure_range());
18819 assert_eq!(result.name.as_deref(), Some("weight"));
18820 assert!(result.same_measure_family(weight_measure_type().as_ref()));
18821 }
18822
18823 #[test]
18824 fn arithmetic_date_range_plus_duration_yields_duration_span_not_date_range() {
18825 let date_range = Arc::new(LemmaType::primitive(TypeSpecification::date_range()));
18826 let duration = duration_like_measure_type();
18827 let unit_index = UnitIndex::new();
18828 let signature_index = crate::computation::arithmetic::SignatureIndex::new();
18829 let named = IndexMap::new();
18830 let scope = MeasureScope {
18831 unit_index: &unit_index,
18832 signature_index: &signature_index,
18833 named_types: &named,
18834 };
18835 let result = compute_arithmetic_result_type(
18836 date_range,
18837 &ArithmeticComputation::Add,
18838 duration,
18839 &scope,
18840 );
18841 assert!(result.is_duration_like_measure());
18842 assert!(!result.is_date_range());
18843 assert!(!result.is_date());
18844 }
18845
18846 #[test]
18847 fn arithmetic_date_range_plus_calendar_yields_date_range() {
18848 let date_range = Arc::new(LemmaType::primitive(TypeSpecification::date_range()));
18849 let calendar = calendar_like_measure_type();
18850 let unit_index = UnitIndex::new();
18851 let signature_index = crate::computation::arithmetic::SignatureIndex::new();
18852 let named = IndexMap::new();
18853 let scope = MeasureScope {
18854 unit_index: &unit_index,
18855 signature_index: &signature_index,
18856 named_types: &named,
18857 };
18858 let result = compute_arithmetic_result_type(
18859 date_range,
18860 &ArithmeticComputation::Add,
18861 calendar,
18862 &scope,
18863 );
18864 assert!(result.is_date_range());
18865 }
18866 }
18867}
18868
18869#[derive(Debug, Clone, Default)]
18876pub struct ResolvedSpecTypes {
18877 pub resolved: IndexMap<String, Arc<LemmaType>>,
18880
18881 pub declared_suggestions: IndexMap<String, BoundValueKind>,
18887
18888 pub declared_values: IndexMap<String, BoundValueKind>,
18892
18893 pub(crate) raw_suggestions: Vec<(String, RawSuggestion)>,
18895
18896 pub(crate) raw_values: Vec<(String, RawSuggestion)>,
18898
18899 pub(crate) source_defaults: IndexMap<String, RawSuggestion>,
18901
18902 pub(crate) source_value_defaults: IndexMap<String, RawSuggestion>,
18904
18905 pub(crate) rule_list_types: Vec<(ParentType, Arc<LemmaType>)>,
18908
18909 pub unit_index: crate::planning::unit_index::UnitIndex,
18912
18913 pub(crate) deferred_signature_collisions:
18916 Vec<(String, crate::planning::semantics::BaseMeasureVector)>,
18917
18918 planning_unit_index: PlanningUnitIndex,
18920
18921 pub signature_index: crate::computation::arithmetic::SignatureIndex,
18924}
18925
18926#[derive(Debug, Clone, PartialEq)]
18928pub(crate) struct DataTypeDef {
18929 pub parent: ParentType,
18930 pub constraints: Option<Vec<Constraint>>,
18931 pub source: crate::parsing::source::Source,
18932 pub name: String,
18933 pub bound_literal: Option<ast::Value>,
18935}
18936
18937#[derive(Debug, Clone)]
18941pub(crate) struct TypeResolver<'a> {
18942 data_types: Vec<(&'a LemmaSpec, HashMap<String, DataTypeDef>)>,
18943 context: &'a Context,
18944 all_registered_specs: Vec<(Arc<LemmaRepository>, &'a LemmaSpec)>,
18945 specs_with_failed_registration: Vec<&'a LemmaSpec>,
18950 slices: HashMap<SpecVersionKey, Arc<instance_list::VersionSlices>>,
18952}
18953
18954fn element_parent_type(parent: &ParentType) -> &ParentType {
18956 let mut current = parent;
18957 while let ParentType::Ranged { inner } | ParentType::List { inner } = current {
18958 current = inner.as_ref();
18959 }
18960 current
18961}
18962
18963fn time_range_endpoints_share_timezone(
18964 left: &semantics::SemanticTime,
18965 right: &semantics::SemanticTime,
18966) -> bool {
18967 match (&left.timezone, &right.timezone) {
18968 (None, None) => true,
18969 (Some(left_tz), Some(right_tz)) => left_tz.offset_minutes == right_tz.offset_minutes,
18970 _ => false,
18971 }
18972}
18973
18974fn parser_value_base_type_display_name(value: &ast::Value) -> &'static str {
18978 match value {
18979 ast::Value::Number(_) => "number",
18980 ast::Value::Text(_) => "text",
18981 ast::Value::Boolean(_) => "boolean",
18982 ast::Value::Date(_) => "date",
18983 ast::Value::Time(_) => "time",
18984 ast::Value::NumberWithUnit(_, unit) => match unit.as_str() {
18985 "percent" | "permille" => "ratio",
18986 _ => "measure",
18987 },
18988 ast::Value::Range(_, _) => "range",
18989 }
18990}
18991
18992fn inferred_parent_type_from_literal(value: &ast::Value) -> Result<ParentType, String> {
18997 let parent_type = match value {
18998 ast::Value::Number(_) => ParentType::Primitive {
18999 primitive: PrimitiveKind::Number,
19000 },
19001 ast::Value::Text(_) => ParentType::Primitive {
19002 primitive: PrimitiveKind::Text,
19003 },
19004 ast::Value::Boolean(_) => ParentType::Primitive {
19005 primitive: PrimitiveKind::Boolean,
19006 },
19007 ast::Value::Date(_) => ParentType::Primitive {
19008 primitive: PrimitiveKind::Date,
19009 },
19010 ast::Value::Time(_) => ParentType::Primitive {
19011 primitive: PrimitiveKind::Time,
19012 },
19013 ast::Value::NumberWithUnit(_, _) => ParentType::Primitive {
19014 primitive: PrimitiveKind::Measure,
19015 },
19016 ast::Value::Range(left, right) => {
19017 let primitive = match (left.as_ref(), right.as_ref()) {
19018 (ast::Value::Number(_), ast::Value::Number(_)) => PrimitiveKind::NumberRange,
19019 (ast::Value::Date(_), ast::Value::Date(_)) => PrimitiveKind::DateRange,
19020 (ast::Value::Time(_), ast::Value::Time(_)) => PrimitiveKind::TimeRange,
19021 (ast::Value::NumberWithUnit(_, u1), ast::Value::NumberWithUnit(_, u2))
19022 if u1 == u2 && matches!(u1.as_str(), "percent" | "permille") =>
19023 {
19024 PrimitiveKind::RatioRange
19025 }
19026 (ast::Value::NumberWithUnit(_, _), ast::Value::NumberWithUnit(_, _)) => {
19027 PrimitiveKind::MeasureRange
19028 }
19029 (left_value, right_value) => {
19030 return Err(format!(
19031 "range endpoints must have the same supported base type, got {} and {}",
19032 parser_value_base_type_display_name(left_value),
19033 parser_value_base_type_display_name(right_value)
19034 ));
19035 }
19036 };
19037 ParentType::Primitive { primitive }
19038 }
19039 };
19040 Ok(parent_type)
19041}
19042
19043impl<'a> TypeResolver<'a> {
19044 pub fn new(
19045 context: &'a Context,
19046 slices: HashMap<SpecVersionKey, Arc<instance_list::VersionSlices>>,
19047 ) -> Self {
19048 TypeResolver {
19049 data_types: Vec::new(),
19050 context,
19051 all_registered_specs: Vec::new(),
19052 specs_with_failed_registration: Vec::new(),
19053 slices,
19054 }
19055 }
19056
19057 fn identity_at(
19059 &self,
19060 repository: &LemmaRepository,
19061 spec: &LemmaSpec,
19062 at: &EffectiveDate,
19063 ) -> SpecIdentity {
19064 self.slices
19065 .get(&SpecVersionKey::of(repository, spec))
19066 .expect("BUG: a spec type resolution reaches is in the graph's dependency order")
19067 .identity_at(repository, spec, at)
19068 }
19069
19070 fn rule_spec_element_list_type(
19073 &self,
19074 spec: &LemmaSpec,
19075 parent: &ParentType,
19076 source: &Source,
19077 at: &EffectiveDate,
19078 ) -> Option<Arc<LemmaType>> {
19079 let (name, spec_ref) = Self::list_of_spec_name(spec, parent)?;
19080 let (row_repository, row_spec) = match discovery::resolve_spec_ref_or_self(
19081 self.context,
19082 &spec_ref,
19083 self.registered_repository(spec),
19084 spec,
19085 at,
19086 Some(source.clone()),
19087 ) {
19088 Ok(pair) => pair,
19089 Err(_) => return None,
19090 };
19091 let identity = self.identity_at(&row_repository, row_spec, &spec_ref.at(at));
19092 let element = Arc::new(LemmaType::primitive(TypeSpecification::Spec {
19093 spec: identity,
19094 }));
19095 Some(Arc::new(LemmaType {
19096 name: Some(format!("{name} list")),
19097 specifications: TypeSpecification::List {
19098 element,
19099 key: ListKey::Numbered,
19100 help: String::new(),
19101 },
19102 extends: TypeExtends::Primitive,
19103 options: TypeOptions::default(),
19104 }))
19105 }
19106
19107 fn list_of_spec_name<'p>(
19111 spec: &LemmaSpec,
19112 parent: &'p ParentType,
19113 ) -> Option<(&'p str, ast::SpecRef)> {
19114 let ParentType::List { inner } = parent else {
19115 return None;
19116 };
19117 let ParentType::Custom { name } = inner.as_ref() else {
19118 return None;
19119 };
19120 discovery::list_element_spec_ref(spec, inner).map(|spec_ref| (name.as_str(), spec_ref))
19121 }
19122
19123 pub fn is_registered(&self, spec: &LemmaSpec) -> bool {
19124 self.all_registered_specs
19125 .iter()
19126 .any(|(_, s)| discovery::same_loaded_spec(s, spec))
19127 }
19128
19129 fn registered_repository(&self, spec: &LemmaSpec) -> &Arc<LemmaRepository> {
19131 self.all_registered_specs
19132 .iter()
19133 .find(|(_, registered)| discovery::same_loaded_spec(registered, spec))
19134 .map(|(repository, _)| repository)
19135 .expect("BUG: types are resolved only for specs registered with their repository")
19136 }
19137
19138 fn mark_registration_failure(&mut self, spec: &'a LemmaSpec) {
19140 if !self
19141 .specs_with_failed_registration
19142 .iter()
19143 .any(|s| discovery::same_loaded_spec(s, spec))
19144 {
19145 self.specs_with_failed_registration.push(spec);
19146 }
19147 }
19148
19149 pub fn registration_failed(&self, spec: &LemmaSpec) -> bool {
19154 self.specs_with_failed_registration
19155 .iter()
19156 .any(|s| discovery::same_loaded_spec(s, spec))
19157 }
19158
19159 pub fn register_all(
19161 &mut self,
19162 repository: &Arc<LemmaRepository>,
19163 spec: &'a LemmaSpec,
19164 ) -> Vec<Error> {
19165 self.record_repository(repository, spec);
19166
19167 let mut errors = Vec::new();
19168 for data in &spec.data {
19169 match &data.value {
19170 ParsedDataValue::Definition {
19171 base,
19172 constraints,
19173 value,
19174 list: _,
19175 } => {
19176 if matches!(
19177 (base.as_ref(), constraints.as_ref(), value.as_ref()),
19178 (None, None, Some(Value::NumberWithUnit(_, _)),)
19179 ) {
19180 continue;
19181 }
19182 let name = &data.reference.name;
19183 let parent = match (base.as_ref(), value.as_ref()) {
19184 (Some(b), _) => b.clone(),
19185 (None, Some(v)) => match inferred_parent_type_from_literal(v) {
19186 Ok(parent) => parent,
19187 Err(message) => {
19188 errors.push(Error::validation_with_context(
19189 message,
19190 Some(data.source_location.clone()),
19191 None::<String>,
19192 Some(spec),
19193 None,
19194 ));
19195 self.mark_registration_failure(spec);
19196 continue;
19197 }
19198 },
19199 (None, None) => {
19200 errors.push(Error::validation_with_context(
19201 format!(
19202 "Data '{name}' in spec '{}' must declare a type or a literal value",
19203 spec.name
19204 ),
19205 Some(data.source_location.clone()),
19206 None::<String>,
19207 Some(spec),
19208 None,
19209 ));
19210 self.mark_registration_failure(spec);
19211 continue;
19212 }
19213 };
19214 if Self::list_of_spec_name(spec, &parent).is_some() {
19217 continue;
19218 }
19219 let ftd = DataTypeDef {
19220 parent,
19221 constraints: constraints.clone(),
19222 source: data.source_location.clone(),
19223 name: name.clone(),
19224 bound_literal: value.clone(),
19225 };
19226 if let Err(e) = self.register_type(repository, spec, ftd) {
19227 errors.push(e);
19228 }
19229 }
19230 ParsedDataValue::Import { .. } | ParsedDataValue::ImportList { .. } => {}
19231 }
19232 }
19233 errors
19234 }
19235
19236 fn record_repository(&mut self, repository: &Arc<LemmaRepository>, spec: &'a LemmaSpec) {
19237 if !self
19238 .all_registered_specs
19239 .iter()
19240 .any(|(_, s)| discovery::same_loaded_spec(s, spec))
19241 {
19242 self.all_registered_specs
19243 .push((Arc::clone(repository), spec));
19244 }
19245 }
19246
19247 pub fn register_type(
19249 &mut self,
19250 repository: &Arc<LemmaRepository>,
19251 spec: &'a LemmaSpec,
19252 def: DataTypeDef,
19253 ) -> Result<(), Error> {
19254 self.record_repository(repository, spec);
19255 let spec_types = if let Some(pos) = self
19256 .data_types
19257 .iter()
19258 .position(|(s, _)| discovery::same_loaded_spec(s, spec))
19259 {
19260 &mut self.data_types[pos].1
19261 } else {
19262 self.data_types.push((spec, HashMap::new()));
19263 let last = self.data_types.len() - 1;
19264 &mut self.data_types[last].1
19265 };
19266 if spec_types.contains_key(&def.name) {
19267 return Err(Error::validation_with_context(
19268 format!(
19269 "The name '{}' is already used for data in this spec.",
19270 def.name
19271 ),
19272 Some(def.source.clone()),
19273 None::<String>,
19274 Some(spec),
19275 None,
19276 ));
19277 }
19278 spec_types.insert(def.name.clone(), def);
19279 Ok(())
19280 }
19281
19282 pub fn resolve_and_validate(
19285 &self,
19286 spec: &LemmaSpec,
19287 at: &EffectiveDate,
19288 already_resolved: &ResolvedTypesMap,
19289 ) -> Result<ResolvedSpecTypes, Vec<Error>> {
19290 let (mut resolved_types, list_type_names) =
19291 self.resolve_types_internal(spec, at, already_resolved)?;
19292 resolved_types.source_defaults = resolved_types
19293 .raw_suggestions
19294 .iter()
19295 .map(|(name, raw)| (name.clone(), raw.clone()))
19296 .collect();
19297 resolved_types.source_value_defaults = resolved_types
19298 .raw_values
19299 .iter()
19300 .map(|(name, raw)| (name.clone(), raw.clone()))
19301 .collect();
19302 let mut errors = Vec::new();
19303
19304 let type_sources: std::collections::HashMap<String, Source> = resolved_types
19306 .resolved
19307 .keys()
19308 .chain(list_type_names.iter())
19309 .filter_map(|type_name| {
19310 self.data_types
19311 .iter()
19312 .find(|(s, _)| discovery::same_loaded_spec(s, spec))
19313 .and_then(|(_, defs)| defs.get(type_name.as_str()))
19314 .map(|ftd| (type_name.clone(), ftd.source.clone()))
19315 })
19316 .collect();
19317
19318 errors.extend(rewrite_unit_path_scale_payloads(
19320 spec,
19321 &mut resolved_types.resolved,
19322 &resolved_types.planning_unit_index,
19323 &type_sources,
19324 ));
19325 let this_spec = SpecVersionKey::of(self.registered_repository(spec), spec);
19326 let (new_resolved, decomp_errors) = resolve_measure_decompositions(
19327 &spec.name,
19328 &this_spec,
19329 std::mem::take(&mut resolved_types.resolved),
19330 &resolved_types.planning_unit_index,
19331 &type_sources,
19332 );
19333 resolved_types.resolved = new_resolved;
19334 errors.extend(decomp_errors);
19335
19336 for (type_name, raw) in std::mem::take(&mut resolved_types.raw_suggestions) {
19337 let lemma_type = resolved_types
19338 .resolved
19339 .get(&type_name)
19340 .expect("BUG: raw default for type not in resolved");
19341 match bound_value_kind_from_raw_suggestion(raw, &lemma_type.specifications, &type_name)
19342 {
19343 Ok(value_kind) => {
19344 resolved_types
19345 .declared_suggestions
19346 .insert(type_name, value_kind);
19347 }
19348 Err(message) => {
19349 let source = type_sources
19350 .get(&type_name)
19351 .cloned()
19352 .unwrap_or_else(|| unreachable!("BUG: type '{}' has no source", type_name));
19353 errors.push(Error::validation_with_context(
19354 message,
19355 Some(source),
19356 None::<String>,
19357 Some(spec),
19358 None,
19359 ));
19360 }
19361 }
19362 }
19363
19364 for (type_name, raw) in std::mem::take(&mut resolved_types.raw_values) {
19365 let lemma_type = resolved_types
19366 .resolved
19367 .get(&type_name)
19368 .expect("BUG: raw fill for type not in resolved");
19369 match bound_value_kind_from_raw_suggestion(raw, &lemma_type.specifications, &type_name)
19370 {
19371 Ok(value_kind) => {
19372 resolved_types.declared_values.insert(type_name, value_kind);
19373 }
19374 Err(message) => {
19375 let source = type_sources
19376 .get(&type_name)
19377 .cloned()
19378 .unwrap_or_else(|| unreachable!("BUG: type '{}' has no source", type_name));
19379 errors.push(Error::validation_with_context(
19380 message,
19381 Some(source),
19382 None::<String>,
19383 Some(spec),
19384 None,
19385 ));
19386 }
19387 }
19388 }
19389
19390 if let Some((_, data_defs)) = self
19391 .data_types
19392 .iter()
19393 .find(|(s, _)| discovery::same_loaded_spec(s, spec))
19394 {
19395 errors.extend(refresh_named_range_specs(
19396 self,
19397 spec,
19398 data_defs,
19399 &mut resolved_types,
19400 already_resolved,
19401 at,
19402 ));
19403 errors.extend(apply_deferred_named_range_constraints(
19404 spec,
19405 data_defs,
19406 &mut resolved_types.resolved,
19407 &mut resolved_types.declared_suggestions,
19408 &mut resolved_types.declared_values,
19409 &type_sources,
19410 &resolved_types.planning_unit_index,
19411 ));
19412 }
19413
19414 let (new_resolved, resolved_errors) = finalize_measure_magnitudes_in_resolved(
19415 std::mem::take(&mut resolved_types.resolved),
19416 &resolved_types.declared_suggestions,
19417 &type_sources,
19418 &spec.name,
19419 spec,
19420 );
19421 resolved_types.resolved = new_resolved;
19422
19423 resolved_types.unit_index = std::mem::take(&mut resolved_types.planning_unit_index)
19424 .bind(&resolved_types.resolved, &this_spec);
19425
19426 match build_signature_index(&spec.name, &resolved_types.unit_index, already_resolved) {
19427 Ok(built) => {
19428 resolved_types.signature_index = built.index;
19429 resolved_types.deferred_signature_collisions = built.deferred_cross_type;
19430 }
19431 Err(error) => errors.push(error),
19432 }
19433
19434 if !resolved_errors.is_empty() {
19435 errors.extend(resolved_errors);
19436 return Err(errors);
19437 }
19438
19439 if let Err(list_errors) = self.resolve_list_types(
19440 spec,
19441 at,
19442 already_resolved,
19443 &list_type_names,
19444 &mut resolved_types,
19445 ) {
19446 errors.extend(list_errors);
19447 return Err(errors);
19448 }
19449
19450 if let Err(rule_list_errors) =
19451 self.resolve_rule_list_types(spec, at, already_resolved, &mut resolved_types)
19452 {
19453 errors.extend(rule_list_errors);
19454 return Err(errors);
19455 }
19456
19457 let mut validated_in_unit_index: HashSet<String> = HashSet::new();
19458 for lemma_type in resolved_types.unit_index.values() {
19459 let Some(measure_family) = lemma_type.measure_family_name() else {
19460 continue;
19461 };
19462 if !lemma_type.is_measure()
19463 || !validated_in_unit_index.insert(measure_family.to_string())
19464 {
19465 continue;
19466 }
19467 let type_name = lemma_type
19468 .name
19469 .as_deref()
19470 .filter(|name| *name == measure_family)
19471 .unwrap_or(measure_family);
19472 let source = type_sources
19473 .get(type_name)
19474 .cloned()
19475 .or_else(|| {
19476 self.data_types
19477 .iter()
19478 .find_map(|(_, defs)| defs.get(type_name).map(|def| def.source.clone()))
19479 })
19480 .unwrap_or_else(|| {
19481 unreachable!(
19482 "BUG: measure type '{}' in unit_index has no DataTypeDef source",
19483 type_name
19484 )
19485 });
19486 errors.extend(validate_type_specifications(
19487 &lemma_type.specifications,
19488 resolved_types.declared_suggestions.get(type_name),
19489 "suggestion",
19490 type_name,
19491 &source,
19492 Some(spec),
19493 &resolved_types.unit_index,
19494 ));
19495 errors.extend(validate_type_specifications(
19496 &lemma_type.specifications,
19497 resolved_types.declared_values.get(type_name),
19498 "with",
19499 type_name,
19500 &source,
19501 Some(spec),
19502 &resolved_types.unit_index,
19503 ));
19504 }
19505
19506 for (type_name, lemma_type) in &resolved_types.resolved {
19507 let source = type_sources.get(type_name).cloned().unwrap_or_else(|| {
19508 unreachable!(
19509 "BUG: resolved type '{}' has no corresponding DataTypeDef in spec '{}'",
19510 type_name, spec.name
19511 )
19512 });
19513 let mut spec_errors = validate_type_specifications(
19514 &lemma_type.specifications,
19515 resolved_types.declared_suggestions.get(type_name),
19516 "suggestion",
19517 type_name,
19518 &source,
19519 Some(spec),
19520 &resolved_types.unit_index,
19521 );
19522 errors.append(&mut spec_errors);
19523 let mut fill_errors = validate_type_specifications(
19524 &lemma_type.specifications,
19525 resolved_types.declared_values.get(type_name),
19526 "with",
19527 type_name,
19528 &source,
19529 Some(spec),
19530 &resolved_types.unit_index,
19531 );
19532 errors.append(&mut fill_errors);
19533 errors.extend(validate_type_options(
19534 lemma_type,
19535 resolved_types.declared_suggestions.get(type_name),
19536 "suggestion",
19537 type_name,
19538 &source,
19539 Some(spec),
19540 &resolved_types.unit_index,
19541 ));
19542 errors.extend(validate_type_options(
19543 lemma_type,
19544 resolved_types.declared_values.get(type_name),
19545 "with",
19546 type_name,
19547 &source,
19548 Some(spec),
19549 &resolved_types.unit_index,
19550 ));
19551 }
19552
19553 for (type_name, lemma_type) in &resolved_types.resolved {
19554 let is_range = matches!(
19555 &lemma_type.specifications,
19556 TypeSpecification::NumberRange { .. }
19557 | TypeSpecification::DateRange { .. }
19558 | TypeSpecification::TimeRange { .. }
19559 | TypeSpecification::MeasureRange { .. }
19560 | TypeSpecification::RatioRange { .. }
19561 );
19562 if !is_range {
19563 continue;
19564 }
19565 let source = type_sources.get(type_name).cloned().unwrap_or_else(|| {
19566 unreachable!(
19567 "BUG: resolved type '{}' has no corresponding DataTypeDef in spec '{}'",
19568 type_name, spec.name
19569 )
19570 });
19571 if let Some(default_kind) = resolved_types.declared_suggestions.get(type_name.as_str())
19572 {
19573 let lit = TypedLiteral {
19574 value: default_kind.value.clone(),
19575 lemma_type: Arc::clone(lemma_type),
19576 };
19577 if let Err(message) = crate::planning::execution_plan::validate_value_against_type(
19578 lemma_type.as_ref(),
19579 &lit.to_literal(),
19580 &resolved_types.unit_index,
19581 ) {
19582 errors.push(Error::validation_with_context(
19583 format!("Type '{}' suggestion is invalid: {}", type_name, message),
19584 Some(source.clone()),
19585 None::<String>,
19586 Some(spec),
19587 None,
19588 ));
19589 }
19590 }
19591 if let Some(default_kind) = resolved_types.declared_values.get(type_name.as_str()) {
19592 let lit = TypedLiteral {
19593 value: default_kind.value.clone(),
19594 lemma_type: Arc::clone(lemma_type),
19595 };
19596 if let Err(message) = crate::planning::execution_plan::validate_value_against_type(
19597 lemma_type.as_ref(),
19598 &lit.to_literal(),
19599 &resolved_types.unit_index,
19600 ) {
19601 errors.push(Error::validation_with_context(
19602 format!("Type '{}' with is invalid: {}", type_name, message),
19603 Some(source),
19604 None::<String>,
19605 Some(spec),
19606 None,
19607 ));
19608 }
19609 }
19610 }
19611
19612 if errors.is_empty() {
19613 Ok(resolved_types)
19614 } else {
19615 Err(errors)
19616 }
19617 }
19618
19619 fn resolve_list_types(
19625 &self,
19626 spec: &LemmaSpec,
19627 at: &EffectiveDate,
19628 already_resolved: &ResolvedTypesMap,
19629 list_type_names: &[String],
19630 resolved_types: &mut ResolvedSpecTypes,
19631 ) -> Result<(), Vec<Error>> {
19632 if list_type_names.is_empty() {
19633 return Ok(());
19634 }
19635 let data_defs = self
19636 .data_types
19637 .iter()
19638 .find(|(s, _)| discovery::same_loaded_spec(s, spec))
19639 .map(|(_, defs)| defs)
19640 .expect("BUG: list type names come from this spec's type definitions");
19641 let mut local = local_type_entries(resolved_types);
19642 let mut errors = Vec::new();
19643 for type_name in list_type_names {
19644 let ftd = data_defs
19645 .get(type_name.as_str())
19646 .expect("BUG: topo-sorted list type missing from registry");
19647 let (lemma_type, suggestion, value) =
19648 self.resolve_type_def(spec, type_name, ftd, at, already_resolved, &local)?;
19649 let to_error = |message: String| {
19650 Error::validation_with_context(
19651 message,
19652 Some(ftd.source.clone()),
19653 None::<String>,
19654 Some(spec),
19655 None,
19656 )
19657 };
19658 if let Some(raw) = &suggestion {
19659 resolved_types
19660 .source_defaults
19661 .insert(type_name.clone(), raw.clone());
19662 match bound_value_kind_from_raw_suggestion(
19663 raw.clone(),
19664 &lemma_type.specifications,
19665 type_name,
19666 ) {
19667 Ok(bound) => {
19668 resolved_types
19669 .declared_suggestions
19670 .insert(type_name.clone(), bound);
19671 }
19672 Err(message) => errors.push(to_error(message)),
19673 }
19674 }
19675 if let Some(raw) = &value {
19676 resolved_types
19677 .source_value_defaults
19678 .insert(type_name.clone(), raw.clone());
19679 match bound_value_kind_from_raw_suggestion(
19680 raw.clone(),
19681 &lemma_type.specifications,
19682 type_name,
19683 ) {
19684 Ok(bound) => {
19685 resolved_types
19686 .declared_values
19687 .insert(type_name.clone(), bound);
19688 }
19689 Err(message) => errors.push(to_error(message)),
19690 }
19691 }
19692 resolved_types
19693 .resolved
19694 .insert(type_name.clone(), Arc::clone(&lemma_type));
19695 local.insert(type_name.clone(), (lemma_type, suggestion, value));
19696 }
19697 if errors.is_empty() {
19698 Ok(())
19699 } else {
19700 Err(errors)
19701 }
19702 }
19703
19704 fn resolve_rule_list_types<'s>(
19707 &self,
19708 spec: &'s LemmaSpec,
19709 at: &EffectiveDate,
19710 already_resolved: &ResolvedTypesMap,
19711 resolved_types: &mut ResolvedSpecTypes,
19712 ) -> Result<(), Vec<Error>> {
19713 let mut written: Vec<(&'s ParentType, &'s Source)> = Vec::new();
19714 for rule in &spec.rules {
19715 let trees = std::iter::once(&rule.expression).chain(
19716 rule.unless_clauses
19717 .iter()
19718 .flat_map(|clause| [&clause.condition, &clause.result]),
19719 );
19720 for tree in trees {
19721 let mut pending = vec![tree.root()];
19722 while let Some(node) = pending.pop() {
19723 if let ast::ExpressionKind::ListType { element } = node.kind() {
19724 if written.iter().all(|(seen, _)| *seen != element) {
19725 let source = node
19726 .source()
19727 .expect("BUG: AST expression missing source location");
19728 written.push((element, source));
19729 }
19730 }
19731 node.push_children(&mut pending);
19732 }
19733 }
19734 }
19735 if written.is_empty() {
19736 return Ok(());
19737 }
19738 let local = local_type_entries(resolved_types);
19739 let mut errors = Vec::new();
19740 for (element, source) in written {
19741 let scope = ParentLookup {
19742 resolver: self,
19743 spec,
19744 source,
19745 at,
19746 already_resolved,
19747 resolved: &local,
19748 site: TypeUseSite::RuleListType,
19749 };
19750 match lookup_parent_type(&scope, element) {
19751 Ok((.., lemma_type)) => {
19752 resolved_types
19753 .rule_list_types
19754 .push((element.clone(), lemma_type));
19755 }
19756 Err(lookup_errors) => {
19757 if let Some(list_type) =
19758 self.rule_spec_element_list_type(spec, element, source, at)
19759 {
19760 resolved_types
19761 .rule_list_types
19762 .push((element.clone(), list_type));
19763 } else {
19764 errors.extend(lookup_errors);
19765 }
19766 }
19767 }
19768 }
19769 if errors.is_empty() {
19770 Ok(())
19771 } else {
19772 Err(errors)
19773 }
19774 }
19775
19776 fn resolve_type_def(
19778 &self,
19779 spec: &LemmaSpec,
19780 type_name: &str,
19781 ftd: &DataTypeDef,
19782 at: &EffectiveDate,
19783 already_resolved: &ResolvedTypesMap,
19784 resolved: &HashMap<String, ResolvedTypeEntry>,
19785 ) -> Result<ResolvedTypeEntry, Vec<Error>> {
19786 let parent = &ftd.parent;
19787 let constraints = &ftd.constraints;
19788
19789 let scope = ParentLookup {
19790 resolver: self,
19791 spec,
19792 source: &ftd.source,
19793 at,
19794 already_resolved,
19795 resolved,
19796 site: TypeUseSite::DataDefinition,
19797 };
19798 let (parent_specs, parent_suggestion, parent_value, parent_type) =
19799 lookup_parent_type(&scope, parent)?;
19800
19801 let mut declared_suggestion = parent_suggestion;
19802 let mut declared_value = parent_value;
19803 let mut final_type = (*parent_type).clone();
19804 final_type.specifications = parent_specs;
19805 if !should_defer_ranged_constraints(parent) {
19806 if let Some(constraints) = &constraints {
19807 let resolve_unit = UnitTokenResolver::Search(
19808 already_resolved
19809 .iter()
19810 .map(|(_, _, rts)| (&rts.unit_index, &rts.resolved))
19811 .collect(),
19812 );
19813 apply_constraints_to_spec(
19814 spec,
19815 &constraint_application_type_name(parent, type_name),
19816 type_name,
19817 &mut final_type,
19818 constraints,
19819 &ftd.source,
19820 ConstraintSuggestionSink {
19821 suggestion: &mut declared_suggestion,
19822 value: &mut declared_value,
19823 resolve_unit: Some(&resolve_unit),
19824 },
19825 )?;
19826 }
19827 }
19828 let final_specs = final_type.specifications;
19829 let final_options = final_type.options;
19830
19831 if matches!(&final_specs, TypeSpecification::List { .. }) && declared_value.is_none() {
19832 declared_value = Some(RawSuggestion::List(Vec::new()));
19833 }
19834
19835 let this_spec = SpecVersionKey::of(self.registered_repository(spec), spec);
19836 let family = match (element_parent_type(parent), parent_type.family()) {
19837 (ParentType::Primitive { .. }, _) | (_, None) => {
19838 TypeFamily::declared_in(&this_spec, type_name)
19839 }
19840 (_, Some(parent_family)) => parent_family.clone(),
19841 };
19842 let defining_spec = match element_parent_type(parent) {
19843 ParentType::Qualified { .. } => TypeDefiningSpec::Import,
19844 _ => TypeDefiningSpec::Local,
19845 };
19846 let extends = TypeExtends::Custom {
19847 parent: parent.to_string(),
19848 family,
19849 defining_spec,
19850 };
19851
19852 let lemma_type = Arc::new(LemmaType {
19853 name: Some(type_name.to_string()),
19854 specifications: final_specs,
19855 extends,
19856 options: final_options,
19857 });
19858
19859 let declared_suggestion = match &ftd.bound_literal {
19860 Some(literal) => {
19861 let resolve_unit = UnitTokenResolver::Search(
19862 already_resolved
19863 .iter()
19864 .map(|(_, _, rts)| (&rts.unit_index, &rts.resolved))
19865 .collect(),
19866 );
19867 match semantics::parser_value_to_value_kind(
19868 literal,
19869 lemma_type.as_ref(),
19870 Some(&resolve_unit),
19871 ) {
19872 Ok(value_kind) => Some(RawSuggestion::Value(value_kind)),
19873 Err(message) => {
19874 return Err(vec![Error::validation_with_context(
19875 message,
19876 Some(ftd.source.clone()),
19877 None::<String>,
19878 Some(spec),
19879 None,
19880 )]);
19881 }
19882 }
19883 }
19884 None => declared_suggestion,
19885 };
19886
19887 Ok((lemma_type, declared_suggestion, declared_value))
19888 }
19889
19890 fn resolve_types_internal(
19893 &self,
19894 spec: &LemmaSpec,
19895 at: &EffectiveDate,
19896 already_resolved: &ResolvedTypesMap,
19897 ) -> Result<(ResolvedSpecTypes, Vec<String>), Vec<Error>> {
19898 let data_defs = self
19899 .data_types
19900 .iter()
19901 .find(|(s, _)| discovery::same_loaded_spec(s, spec))
19902 .map(|(_, defs)| defs)
19903 .cloned()
19904 .unwrap_or_default();
19905
19906 let (scalar_names, list_names) = partition_list_type_names(&data_defs);
19907 let sorted_type_names = topo_sort_type_names(spec, &data_defs, scalar_names)?;
19908 let list_type_names = topo_sort_type_names(spec, &data_defs, list_names)?;
19909
19910 let mut resolved: HashMap<String, ResolvedTypeEntry> = HashMap::new();
19911
19912 for type_name in &sorted_type_names {
19913 let ftd = data_defs
19914 .get(type_name.as_str())
19915 .expect("BUG: topo-sorted type missing from registry");
19916 let entry =
19917 self.resolve_type_def(spec, type_name, ftd, at, already_resolved, &resolved)?;
19918 resolved.insert(type_name.clone(), entry);
19919 }
19920
19921 let mut unit_index = PlanningUnitIndex::new();
19922 let mut errors = Vec::new();
19923 for unit in Self::extract_units_from_type(&semantics::primitive_ratio_arc().specifications)
19924 {
19925 unit_index.insert_owner(unit, PlanningOwner::PrimitiveRatio);
19926 }
19927
19928 let locals: HashMap<String, Arc<LemmaType>> = resolved
19929 .iter()
19930 .map(|(name, (type_arc, _, _))| (name.clone(), Arc::clone(type_arc)))
19931 .collect();
19932
19933 for type_name in &sorted_type_names {
19934 let (type_arc, _, _) = resolved
19935 .get(type_name)
19936 .expect("BUG: type was resolved but missing from map");
19937 let data_type_def = data_defs
19938 .get(type_name.as_str())
19939 .expect("BUG: type was resolved but not in registry");
19940 let merge_result = if type_arc.is_measure() {
19941 if let ParentType::Qualified { spec_alias, inner } = &data_type_def.parent {
19942 match Self::parent_units_for_qualified_extension(
19943 self,
19944 spec,
19945 data_type_def,
19946 already_resolved,
19947 at,
19948 spec_alias,
19949 inner,
19950 ) {
19951 Ok(None) => Ok(()),
19952 Ok(Some(parent_units)) => Self::merge_additive_measure_units(
19953 spec,
19954 &mut unit_index,
19955 type_arc,
19956 data_type_def,
19957 &[],
19958 &parent_units,
19959 &locals,
19960 ),
19961 Err(error) => Err(error),
19962 }
19963 } else {
19964 Self::add_measure_units_to_index(
19965 spec,
19966 &mut unit_index,
19967 type_arc,
19968 data_type_def,
19969 &[],
19970 &locals,
19971 )
19972 }
19973 } else if type_arc.is_ratio() {
19974 Self::add_ratio_units_to_index(
19975 spec,
19976 &mut unit_index,
19977 type_arc,
19978 data_type_def,
19979 &[],
19980 &locals,
19981 )
19982 } else {
19983 Ok(())
19984 };
19985 if let Err(error) = merge_result {
19986 errors.push(error);
19987 }
19988 }
19989
19990 for data_row in &spec.data {
19991 let import_alias = data_row.reference.name.clone();
19992 let mut merge_imported_units = |imported_resolved: &ResolvedSpecTypes| {
19993 for (bare, owner) in imported_resolved.unit_index.iter_owners() {
19994 let planning_owner = match owner {
19995 UnitOwner::PrimitiveRatio => PlanningOwner::PrimitiveRatio,
19996 UnitOwner::Named {
19997 import_path,
19998 type_name,
19999 lemma_type,
20000 } => {
20001 let mut path = vec![import_alias.clone()];
20002 path.extend(import_path.iter().cloned());
20003 PlanningOwner::Imported {
20004 import_path: path,
20005 type_name: type_name.clone(),
20006 lemma_type: Arc::clone(lemma_type),
20007 }
20008 }
20009 };
20010 unit_index.insert_owner(bare.to_string(), planning_owner);
20011 }
20012 };
20013 match &data_row.value {
20014 ParsedDataValue::Import {
20015 spec_ref,
20016 effective_ref: Some(_),
20017 ..
20018 } => {
20019 let resolved = match discovery::resolve_spec_ref_repository(
20021 self.context,
20022 spec_ref,
20023 Some(&data_row.source_location),
20024 spec.name.as_str(),
20025 Some(spec),
20026 ) {
20027 Ok(resolved) => resolved,
20028 Err(error) => {
20029 errors.push(error);
20030 continue;
20031 }
20032 };
20033 let repository = match &resolved.repository {
20034 Some(explicit) => Arc::clone(explicit),
20035 None => Arc::clone(self.registered_repository(spec)),
20036 };
20037 let dep_name =
20038 crate::parsing::ast::ascii_lowercase_logical_name(spec_ref.name.clone());
20039 for (repo, imported, imported_resolved) in already_resolved {
20040 if !Arc::ptr_eq(repo, &repository) {
20041 continue;
20042 }
20043 if crate::parsing::ast::ascii_lowercase_logical_name(imported.name.clone())
20044 != dep_name
20045 {
20046 continue;
20047 }
20048 if discovery::same_loaded_spec(imported, spec) {
20049 continue;
20050 }
20051 merge_imported_units(imported_resolved);
20052 }
20053 }
20054 ParsedDataValue::Import { .. } => {
20055 let Some(spec_ref) = data_row.value.uses_spec_ref() else {
20056 continue;
20057 };
20058 let (_, imported_spec) = match self.resolve_spec_for_import(
20059 spec,
20060 spec_ref,
20061 &data_row.source_location,
20062 at,
20063 ) {
20064 Ok(pair) => pair,
20065 Err(error) => {
20066 errors.push(error);
20067 continue;
20068 }
20069 };
20070 if discovery::same_loaded_spec(imported_spec, spec) {
20071 continue;
20072 }
20073 let Some(imported_resolved) = already_resolved
20074 .iter()
20075 .find(|(_, imported, _)| {
20076 discovery::same_loaded_spec(imported, imported_spec)
20077 })
20078 .map(|(_, _, resolved_spec_types)| resolved_spec_types)
20079 else {
20080 continue;
20081 };
20082 merge_imported_units(imported_resolved);
20083 }
20084 ParsedDataValue::Definition { .. } => {
20085 let Some(spec_ref) = discovery::data_instance_list_spec_ref(spec, data_row)
20086 else {
20087 continue;
20088 };
20089 let ParsedDataValue::Definition {
20090 base: Some(parent), ..
20091 } = &data_row.value
20092 else {
20093 continue;
20094 };
20095 if Self::list_of_spec_name(spec, parent).is_none() {
20096 continue;
20097 }
20098 let (_, imported_spec) = match self.resolve_list_spec_for_import(
20099 spec,
20100 &spec_ref,
20101 &data_row.source_location,
20102 at,
20103 ) {
20104 Ok(pair) => pair,
20105 Err(error) => {
20106 errors.push(error);
20107 continue;
20108 }
20109 };
20110 if discovery::same_loaded_spec(imported_spec, spec) {
20111 continue;
20112 }
20113 let Some(imported_resolved) = already_resolved
20114 .iter()
20115 .find(|(_, imported, _)| {
20116 discovery::same_loaded_spec(imported, imported_spec)
20117 })
20118 .map(|(_, _, resolved_spec_types)| resolved_spec_types)
20119 else {
20120 continue;
20121 };
20122 merge_imported_units(imported_resolved);
20123 }
20124 ParsedDataValue::ImportList { .. } => {
20125 panic!(
20126 "BUG: uses list is rejected at parse and must not reach type resolution"
20127 );
20128 }
20129 }
20130 }
20131
20132 if !errors.is_empty() {
20133 return Err(errors);
20134 }
20135
20136 let mut raw_suggestions = Vec::new();
20137 let mut raw_values = Vec::new();
20138 let mut resolved_types = IndexMap::new();
20139 for type_name in &sorted_type_names {
20140 let (lemma_type, default, fill) = resolved
20141 .remove(type_name)
20142 .expect("BUG: every sorted type name must exist in resolved");
20143 if let Some(raw_suggestion) = default {
20144 raw_suggestions.push((type_name.clone(), raw_suggestion));
20145 }
20146 if let Some(raw_value) = fill {
20147 raw_values.push((type_name.clone(), raw_value));
20148 }
20149 resolved_types.insert(type_name.clone(), lemma_type);
20150 }
20151 if !resolved.is_empty() {
20152 panic!(
20153 "BUG: resolved types remain after draining sorted_type_names: {:?}",
20154 resolved.keys().collect::<Vec<_>>()
20155 );
20156 }
20157
20158 Ok((
20159 ResolvedSpecTypes {
20160 resolved: resolved_types,
20161 declared_suggestions: IndexMap::new(),
20162 declared_values: IndexMap::new(),
20163 raw_suggestions,
20164 raw_values,
20165 source_defaults: IndexMap::new(),
20166 source_value_defaults: IndexMap::new(),
20167 rule_list_types: Vec::new(),
20168 unit_index: UnitIndex::new(),
20169 planning_unit_index: unit_index,
20170 signature_index: crate::computation::arithmetic::SignatureIndex::new(),
20171 deferred_signature_collisions: Vec::new(),
20172 },
20173 list_type_names,
20174 ))
20175 }
20176
20177 fn resolve_spec_for_import(
20178 &self,
20179 spec: &LemmaSpec,
20180 from: &crate::parsing::ast::SpecRef,
20181 import_site: &crate::parsing::source::Source,
20182 at: &EffectiveDate,
20183 ) -> Result<(Arc<LemmaRepository>, &'a LemmaSpec), Error> {
20184 discovery::resolve_spec_ref(
20185 self.context,
20186 from,
20187 self.registered_repository(spec),
20188 spec,
20189 at,
20190 Some(import_site.clone()),
20191 )
20192 }
20193
20194 fn resolve_list_spec_for_import(
20196 &self,
20197 spec: &LemmaSpec,
20198 from: &crate::parsing::ast::SpecRef,
20199 import_site: &crate::parsing::source::Source,
20200 at: &EffectiveDate,
20201 ) -> Result<(Arc<LemmaRepository>, &'a LemmaSpec), Error> {
20202 discovery::resolve_spec_ref_or_self(
20203 self.context,
20204 from,
20205 self.registered_repository(spec),
20206 spec,
20207 at,
20208 Some(import_site.clone()),
20209 )
20210 }
20211
20212 fn add_measure_units_to_index(
20217 spec: &LemmaSpec,
20218 unit_index: &mut PlanningUnitIndex,
20219 resolved_type: &Arc<LemmaType>,
20220 defined_by: &DataTypeDef,
20221 import_path: &[String],
20222 locals: &HashMap<String, Arc<LemmaType>>,
20223 ) -> Result<(), Error> {
20224 if matches!(defined_by.parent, ParentType::Qualified { .. }) {
20225 unreachable!(
20226 "BUG: Qualified parents must use parent_units_for_qualified_extension + merge_additive_measure_units"
20227 );
20228 }
20229 let measure_family = resolved_type
20230 .measure_family_name()
20231 .expect("BUG: add_measure_units_to_index requires measure type with family");
20232 for unit in Self::extract_units_from_type(&resolved_type.specifications) {
20233 unit_index
20234 .merge_measure_unit(
20235 unit,
20236 resolved_type,
20237 &defined_by.name,
20238 import_path,
20239 measure_family,
20240 locals,
20241 )
20242 .map_err(|conflict| {
20243 Self::unit_merge_conflict_to_error(conflict, defined_by, spec)
20244 })?;
20245 }
20246 Ok(())
20247 }
20248
20249 fn parent_units_for_qualified_extension(
20251 resolver: &TypeResolver<'_>,
20252 spec: &LemmaSpec,
20253 defined_by: &DataTypeDef,
20254 already_resolved: &ResolvedTypesMap<'_>,
20255 at: &EffectiveDate,
20256 spec_alias: &str,
20257 inner: &ParentType,
20258 ) -> Result<Option<BTreeSet<String>>, Error> {
20259 let ParentType::Custom {
20260 name: parent_type_name,
20261 ..
20262 } = inner
20263 else {
20264 return Ok(None);
20265 };
20266 let Some(spec_ref) = spec.data.iter().find_map(|row| {
20267 if row.reference.segments.is_empty() && row.reference.name == spec_alias {
20268 row.value.uses_spec_ref()
20269 } else {
20270 None
20271 }
20272 }) else {
20273 return Err(Error::validation_with_context(
20274 format!("'{spec_alias}' is not a `uses` import alias in this spec"),
20275 Some(defined_by.source.clone()),
20276 Some(format!(
20277 "Add `uses {spec_alias}: <target_spec>`, then write `{spec_alias}.<type>`."
20278 )),
20279 Some(spec),
20280 None,
20281 ));
20282 };
20283 let (_, target_spec) =
20284 resolver.resolve_spec_for_import(spec, spec_ref, &defined_by.source, at)?;
20285 let Some(imported_resolved) = already_resolved
20286 .iter()
20287 .find(|(_, imported, _)| discovery::same_loaded_spec(imported, target_spec))
20288 .map(|(_, _, types)| types)
20289 else {
20290 return Ok(None);
20291 };
20292 let Some(parent_type) = imported_resolved.resolved.get(parent_type_name.as_str()) else {
20293 return Ok(None);
20294 };
20295 Ok(Some(
20296 Self::extract_units_from_type(&parent_type.specifications)
20297 .into_iter()
20298 .collect(),
20299 ))
20300 }
20301
20302 fn merge_additive_measure_units(
20304 spec: &LemmaSpec,
20305 unit_index: &mut PlanningUnitIndex,
20306 resolved_type: &Arc<LemmaType>,
20307 defined_by: &DataTypeDef,
20308 import_path: &[String],
20309 parent_units: &BTreeSet<String>,
20310 locals: &HashMap<String, Arc<LemmaType>>,
20311 ) -> Result<(), Error> {
20312 let measure_family = resolved_type
20313 .measure_family_name()
20314 .expect("BUG: additive measure registration requires family");
20315 for unit in Self::extract_units_from_type(&resolved_type.specifications) {
20316 if parent_units.contains(&unit) {
20317 continue;
20318 }
20319 unit_index
20320 .merge_measure_unit(
20321 unit,
20322 resolved_type,
20323 &defined_by.name,
20324 import_path,
20325 measure_family,
20326 locals,
20327 )
20328 .map_err(|conflict| {
20329 Self::unit_merge_conflict_to_error(conflict, defined_by, spec)
20330 })?;
20331 }
20332 Ok(())
20333 }
20334
20335 fn add_ratio_units_to_index(
20336 spec: &LemmaSpec,
20337 unit_index: &mut PlanningUnitIndex,
20338 resolved_type: &Arc<LemmaType>,
20339 defined_by: &DataTypeDef,
20340 import_path: &[String],
20341 locals: &HashMap<String, Arc<LemmaType>>,
20342 ) -> Result<(), Error> {
20343 for unit in Self::extract_units_from_type(&resolved_type.specifications) {
20344 unit_index
20345 .merge_ratio_unit(unit, resolved_type, &defined_by.name, import_path, locals)
20346 .map_err(|conflict| {
20347 Self::unit_merge_conflict_to_error(conflict, defined_by, spec)
20348 })?;
20349 }
20350 Ok(())
20351 }
20352
20353 fn unit_merge_conflict_to_error(
20354 conflict: UnitMergeConflict,
20355 defined_by: &DataTypeDef,
20356 spec: &LemmaSpec,
20357 ) -> Error {
20358 let message = match conflict {
20359 UnitMergeConflict::ConflictingFactors { unit, family } => format!(
20360 "Unit '{unit}' has different values on related measure types in '{family}'. \
20361 Use the same unit value on each type, or use different unit names."
20362 ),
20363 };
20364 Error::validation_with_context(
20365 message,
20366 Some(defined_by.source.clone()),
20367 None::<String>,
20368 Some(spec),
20369 None,
20370 )
20371 }
20372
20373 fn extract_units_from_type(specs: &TypeSpecification) -> Vec<String> {
20374 match specs {
20375 TypeSpecification::Measure { units, .. } => {
20376 units.iter().map(|unit| unit.name.clone()).collect()
20377 }
20378 TypeSpecification::Ratio { units, .. } => {
20379 units.iter().map(|unit| unit.name.clone()).collect()
20380 }
20381 _ => Vec::new(),
20382 }
20383 }
20384}
20385
20386fn instance_list_option_errors(
20391 lemma_type: &LemmaType,
20392 raw: &RawSuggestion,
20393 source: &Source,
20394 spec: &LemmaSpec,
20395) -> Vec<Error> {
20396 let owner = match &lemma_type.specifications {
20397 TypeSpecification::List { element, .. } => element.as_ref(),
20398 _ => lemma_type,
20399 };
20400 let mut values = Vec::new();
20401 let mut errors = Vec::new();
20402 if let Err(message) = collect_raw_values(raw, &mut values) {
20403 errors.push(Error::validation_with_context(
20404 message,
20405 Some(source.clone()),
20406 None::<String>,
20407 Some(spec),
20408 None,
20409 ));
20410 return errors;
20411 }
20412 for value in values {
20413 if let Err(message) = owner.reject_disallowed(&value) {
20414 errors.push(Error::validation_with_context(
20415 message,
20416 Some(source.clone()),
20417 None::<String>,
20418 Some(spec),
20419 None,
20420 ));
20421 }
20422 }
20423 errors
20424}
20425
20426fn collect_raw_values(raw: &RawSuggestion, out: &mut Vec<ValueKind>) -> Result<(), String> {
20427 let mut pending = vec![raw];
20428 while let Some(current) = pending.pop() {
20429 match current {
20430 RawSuggestion::Value(value) => out.push(value.clone()),
20431 RawSuggestion::List(rows) => {
20432 for row in rows.iter().rev() {
20433 pending.push(row);
20434 }
20435 }
20436 RawSuggestion::Measure { .. } | RawSuggestion::UnitBound { .. } => {
20437 return Err(
20438 "an instance-list option row is a value, not a measure bound still awaiting a unit"
20439 .to_string(),
20440 );
20441 }
20442 }
20443 }
20444 Ok(())
20445}
20446
20447fn validate_type_options(
20448 lemma_type: &LemmaType,
20449 declared_default: Option<&BoundValueKind>,
20450 default_label: &str,
20451 type_name: &str,
20452 source: &Source,
20453 spec_context: Option<&LemmaSpec>,
20454 unit_index: &UnitIndex,
20455) -> Vec<Error> {
20456 let owner = match &lemma_type.specifications {
20457 TypeSpecification::List { element, .. } if !element.options.is_empty() => element.as_ref(),
20458 _ => lemma_type,
20459 };
20460 let mut errors = Vec::new();
20461 let mut bare = owner.clone();
20462 bare.options = TypeOptions::default();
20463 for option in &owner.options.values {
20464 let literal = LiteralValue {
20465 value: option.value.value.clone(),
20466 };
20467 if let Err(message) =
20468 execution_plan::validate_value_against_type(&bare, &literal, unit_index)
20469 {
20470 errors.push(Error::validation_with_context(
20471 format!(
20472 "Type '{type_name}' option '{}': {message}",
20473 owner.display_bound(&option.value)
20474 ),
20475 Some(source.clone()),
20476 None::<String>,
20477 spec_context,
20478 None,
20479 ));
20480 }
20481 }
20482 if let Some(default) = declared_default {
20483 if let Err(message) = execution_plan::validate_value_against_type(
20484 lemma_type,
20485 &default.to_literal(),
20486 unit_index,
20487 ) {
20488 errors.push(Error::validation_with_context(
20489 format!("Type '{type_name}' {default_label} value {message}"),
20490 Some(source.clone()),
20491 None::<String>,
20492 spec_context,
20493 None,
20494 ));
20495 }
20496 }
20497 errors
20498}
20499
20500pub fn validate_type_specifications(
20511 specs: &TypeSpecification,
20512 declared_default: Option<&BoundValueKind>,
20513 default_label: &str,
20514 type_name: &str,
20515 source: &Source,
20516 spec_context: Option<&LemmaSpec>,
20517 unit_index: &UnitIndex,
20518) -> Vec<Error> {
20519 let mut errors = Vec::new();
20520 let declared_default = declared_default.map(|bound| &bound.value);
20521
20522 match specs {
20523 TypeSpecification::Measure {
20524 minimum,
20525 maximum,
20526 units,
20527 ..
20528 } => {
20529 if let (Some(min), Some(max)) = (minimum, maximum) {
20531 match (
20532 semantics::measure_declared_bound_to_canonical(
20533 &min.0, &min.1, units, type_name, "minimum",
20534 ),
20535 semantics::measure_declared_bound_to_canonical(
20536 &max.0, &max.1, units, type_name, "maximum",
20537 ),
20538 ) {
20539 (Ok(min_canonical), Ok(max_canonical)) => {
20540 match min_canonical.try_cmp(&max_canonical) {
20541 Ok(std::cmp::Ordering::Greater) => {
20542 errors.push(Error::validation_with_context(
20543 format!(
20544 "Type '{}' has invalid range: minimum {} {} is greater than maximum {} {}",
20545 type_name,
20546 min.0,
20547 min.1,
20548 max.0,
20549 max.1
20550 ),
20551 Some(source.clone()),
20552 None::<String>,
20553 spec_context,
20554 None,
20555 ));
20556 }
20557 Ok(std::cmp::Ordering::Less | std::cmp::Ordering::Equal) => {}
20558 Err(failure) => {
20559 errors.push(Error::validation_with_context(
20560 format!(
20561 "Type '{}' has invalid measure bound: range compare failed: {failure}",
20562 type_name
20563 ),
20564 Some(source.clone()),
20565 None::<String>,
20566 spec_context,
20567 None,
20568 ));
20569 }
20570 }
20571 }
20572 (Err(message), _) | (_, Err(message)) => {
20573 errors.push(Error::validation_with_context(
20574 format!(
20575 "Type '{}' has invalid measure bound: {}",
20576 type_name, message
20577 ),
20578 Some(source.clone()),
20579 None::<String>,
20580 spec_context,
20581 None,
20582 ));
20583 }
20584 }
20585 }
20586
20587 if units.is_empty() {
20591 errors.push(Error::validation_with_context(
20592 format!(
20593 "Type '{}' is a measure type but has no units. Measure types must define at least one unit (e.g. -> unit eur: 1).",
20594 type_name
20595 ),
20596 Some(source.clone()),
20597 None::<String>,
20598 spec_context,
20599 None,
20600 ));
20601 }
20602
20603 if !units.is_empty() {
20605 let mut seen_names: Vec<String> = Vec::new();
20606 for unit in units.iter() {
20607 if unit.name.trim().is_empty() {
20609 errors.push(Error::validation_with_context(
20610 format!(
20611 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
20612 type_name
20613 ),
20614 Some(source.clone()),
20615 None::<String>,
20616 spec_context,
20617 None,
20618 ));
20619 }
20620
20621 if seen_names.contains(&unit.name) {
20623 errors.push(Error::validation_with_context(
20624 format!("Type '{}' has duplicate unit name '{}'. Unit names must be unique within a type.", type_name, unit.name),
20625 Some(source.clone()),
20626 None::<String>,
20627 spec_context,
20628 None,
20629 ));
20630 } else {
20631 seen_names.push(unit.name.clone());
20632 }
20633
20634 if !unit.is_positive_factor() {
20635 let factor = unit.require_literal_factor();
20636 errors.push(Error::validation_with_context(
20637 format!("Type '{}' has unit '{}' with invalid value {}/{}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, factor.numer_to_string(), factor.denom_to_string()),
20638 Some(source.clone()),
20639 None::<String>,
20640 spec_context,
20641 None,
20642 ));
20643 }
20644 }
20645 }
20646 }
20647 TypeSpecification::Number {
20648 minimum,
20649 maximum,
20650 decimals,
20651 ..
20652 } => {
20653 if let (Some(min), Some(max)) = (minimum, maximum) {
20655 match min.try_cmp(max) {
20656 Ok(std::cmp::Ordering::Greater) => {
20657 errors.push(Error::validation_with_context(
20658 format!(
20659 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
20660 type_name, min, max
20661 ),
20662 Some(source.clone()),
20663 None::<String>,
20664 spec_context,
20665 None,
20666 ));
20667 }
20668 Ok(std::cmp::Ordering::Less | std::cmp::Ordering::Equal) => {}
20669 Err(failure) => {
20670 errors.push(Error::validation_with_context(
20671 format!(
20672 "Type '{}' has invalid range: bound compare failed: {failure}",
20673 type_name
20674 ),
20675 Some(source.clone()),
20676 None::<String>,
20677 spec_context,
20678 None,
20679 ));
20680 }
20681 }
20682 }
20683
20684 if let Some(d) = decimals {
20686 if *d > 28 {
20687 errors.push(Error::validation_with_context(
20688 format!(
20689 "Type '{}' has invalid decimals value: {}. Must be between 0 and 28",
20690 type_name, d
20691 ),
20692 Some(source.clone()),
20693 None::<String>,
20694 spec_context,
20695 None,
20696 ));
20697 }
20698 }
20699
20700 if let Some(ValueKind::Number(def)) = declared_default {
20701 if let Some(min) = minimum {
20702 match def.try_cmp(min) {
20703 Ok(std::cmp::Ordering::Less) => {
20704 errors.push(Error::validation_with_context(
20705 format!(
20706 "Type '{}' {default_label} value {} is less than minimum {}",
20707 type_name, def, min
20708 ),
20709 Some(source.clone()),
20710 None::<String>,
20711 spec_context,
20712 None,
20713 ));
20714 }
20715 Ok(std::cmp::Ordering::Equal | std::cmp::Ordering::Greater) => {}
20716 Err(failure) => {
20717 errors.push(Error::validation_with_context(
20718 format!(
20719 "Type '{}' {default_label} bound compare failed: {failure}",
20720 type_name
20721 ),
20722 Some(source.clone()),
20723 None::<String>,
20724 spec_context,
20725 None,
20726 ));
20727 }
20728 }
20729 }
20730 if let Some(max) = maximum {
20731 match def.try_cmp(max) {
20732 Ok(std::cmp::Ordering::Greater) => {
20733 errors.push(Error::validation_with_context(
20734 format!(
20735 "Type '{}' {default_label} value {} is greater than maximum {}",
20736 type_name, def, max
20737 ),
20738 Some(source.clone()),
20739 None::<String>,
20740 spec_context,
20741 None,
20742 ));
20743 }
20744 Ok(std::cmp::Ordering::Less | std::cmp::Ordering::Equal) => {}
20745 Err(failure) => {
20746 errors.push(Error::validation_with_context(
20747 format!(
20748 "Type '{}' {default_label} bound compare failed: {failure}",
20749 type_name
20750 ),
20751 Some(source.clone()),
20752 None::<String>,
20753 spec_context,
20754 None,
20755 ));
20756 }
20757 }
20758 }
20759 }
20760 }
20762
20763 TypeSpecification::Ratio {
20764 minimum,
20765 maximum,
20766 units,
20767 ..
20768 } => {
20769 if let (Some(min), Some(max)) = (minimum, maximum) {
20771 match min.try_cmp(max) {
20772 Ok(std::cmp::Ordering::Greater) => {
20773 errors.push(Error::validation_with_context(
20774 format!(
20775 "Type '{}' has invalid range: minimum {} is greater than maximum {}",
20776 type_name, min, max
20777 ),
20778 Some(source.clone()),
20779 None::<String>,
20780 spec_context,
20781 None,
20782 ));
20783 }
20784 Ok(std::cmp::Ordering::Less | std::cmp::Ordering::Equal) => {}
20785 Err(failure) => {
20786 errors.push(Error::validation_with_context(
20787 format!(
20788 "Type '{}' has invalid range: bound compare failed: {failure}",
20789 type_name
20790 ),
20791 Some(source.clone()),
20792 None::<String>,
20793 spec_context,
20794 None,
20795 ));
20796 }
20797 }
20798 }
20799
20800 if let Some(ValueKind::Ratio(def)) = declared_default {
20801 if let Some(min) = minimum {
20802 match def.try_cmp(min) {
20803 Ok(std::cmp::Ordering::Less) => {
20804 errors.push(Error::validation_with_context(
20805 format!(
20806 "Type '{}' {default_label} value {} is less than minimum {}",
20807 type_name, def, min
20808 ),
20809 Some(source.clone()),
20810 None::<String>,
20811 spec_context,
20812 None,
20813 ));
20814 }
20815 Ok(std::cmp::Ordering::Equal | std::cmp::Ordering::Greater) => {}
20816 Err(failure) => {
20817 errors.push(Error::validation_with_context(
20818 format!(
20819 "Type '{}' {default_label} bound compare failed: {failure}",
20820 type_name
20821 ),
20822 Some(source.clone()),
20823 None::<String>,
20824 spec_context,
20825 None,
20826 ));
20827 }
20828 }
20829 }
20830 if let Some(max) = maximum {
20831 match def.try_cmp(max) {
20832 Ok(std::cmp::Ordering::Greater) => {
20833 errors.push(Error::validation_with_context(
20834 format!(
20835 "Type '{}' {default_label} value {} is greater than maximum {}",
20836 type_name, def, max
20837 ),
20838 Some(source.clone()),
20839 None::<String>,
20840 spec_context,
20841 None,
20842 ));
20843 }
20844 Ok(std::cmp::Ordering::Less | std::cmp::Ordering::Equal) => {}
20845 Err(failure) => {
20846 errors.push(Error::validation_with_context(
20847 format!(
20848 "Type '{}' {default_label} bound compare failed: {failure}",
20849 type_name
20850 ),
20851 Some(source.clone()),
20852 None::<String>,
20853 spec_context,
20854 None,
20855 ));
20856 }
20857 }
20858 }
20859 }
20860
20861 if !units.is_empty() {
20865 let mut seen_names: Vec<String> = Vec::new();
20866 for unit in units.iter() {
20867 if unit.name.trim().is_empty() {
20869 errors.push(Error::validation_with_context(
20870 format!(
20871 "Type '{}' has a unit with empty name. Unit names cannot be empty.",
20872 type_name
20873 ),
20874 Some(source.clone()),
20875 None::<String>,
20876 spec_context,
20877 None,
20878 ));
20879 }
20880
20881 if seen_names.contains(&unit.name) {
20883 errors.push(Error::validation_with_context(
20884 format!("Type '{}' has duplicate unit name '{}'. Unit names must be unique within a type.", type_name, unit.name),
20885 Some(source.clone()),
20886 None::<String>,
20887 spec_context,
20888 None,
20889 ));
20890 } else {
20891 seen_names.push(unit.name.clone());
20892 }
20893
20894 if !unit.value.numer_is_positive() {
20895 errors.push(Error::validation_with_context(
20896 format!("Type '{}' has unit '{}' with invalid value {}/{}. Unit values must be positive (conversion factor relative to type base).", type_name, unit.name, unit.value.numer_to_string(), unit.value.denom_to_string()),
20897 Some(source.clone()),
20898 None::<String>,
20899 spec_context,
20900 None,
20901 ));
20902 }
20903 }
20904 }
20905 }
20906
20907 TypeSpecification::Text { length, .. } => {
20908 if let Some(ValueKind::Text(def)) = declared_default {
20909 let def_len = def.len();
20910
20911 if let Some(len) = length {
20912 if def_len != *len {
20913 errors.push(Error::validation_with_context(
20914 format!("Type '{}' {default_label} value length {} does not match required length {}", type_name, def_len, len),
20915 Some(source.clone()),
20916 None::<String>,
20917 spec_context,
20918 None,
20919 ));
20920 }
20921 }
20922 }
20923 }
20924
20925 TypeSpecification::Date {
20926 minimum, maximum, ..
20927 } => {
20928 if let (Some(min), Some(max)) = (minimum, maximum) {
20930 let min_sem = semantics::date_time_to_semantic(min);
20931 let max_sem = semantics::date_time_to_semantic(max);
20932 if semantics::compare_semantic_dates(&min_sem, &max_sem) == Ordering::Greater {
20933 errors.push(Error::validation_with_context(
20934 format!(
20935 "Type '{}' has invalid date range: minimum {} is after maximum {}",
20936 type_name, min, max
20937 ),
20938 Some(source.clone()),
20939 None::<String>,
20940 spec_context,
20941 None,
20942 ));
20943 }
20944 }
20945
20946 if let Some(ValueKind::Date(def)) = declared_default {
20947 if let Some(min) = minimum {
20948 let min_sem = semantics::date_time_to_semantic(min);
20949 if semantics::compare_semantic_dates(def, &min_sem) == Ordering::Less {
20950 errors.push(Error::validation_with_context(
20951 format!(
20952 "Type '{}' default date {} is before minimum {}",
20953 type_name, def, min
20954 ),
20955 Some(source.clone()),
20956 None::<String>,
20957 spec_context,
20958 None,
20959 ));
20960 }
20961 }
20962 if let Some(max) = maximum {
20963 let max_sem = semantics::date_time_to_semantic(max);
20964 if semantics::compare_semantic_dates(def, &max_sem) == Ordering::Greater {
20965 errors.push(Error::validation_with_context(
20966 format!(
20967 "Type '{}' default date {} is after maximum {}",
20968 type_name, def, max
20969 ),
20970 Some(source.clone()),
20971 None::<String>,
20972 spec_context,
20973 None,
20974 ));
20975 }
20976 }
20977 }
20978 }
20979
20980 TypeSpecification::Time {
20981 minimum, maximum, ..
20982 } => {
20983 if let (Some(min), Some(max)) = (minimum, maximum) {
20985 let min_sem = semantics::time_to_semantic(min);
20986 let max_sem = semantics::time_to_semantic(max);
20987 if semantics::compare_semantic_times(&min_sem, &max_sem) == Ordering::Greater {
20988 errors.push(Error::validation_with_context(
20989 format!(
20990 "Type '{}' has invalid time range: minimum {} is after maximum {}",
20991 type_name, min, max
20992 ),
20993 Some(source.clone()),
20994 None::<String>,
20995 spec_context,
20996 None,
20997 ));
20998 }
20999 }
21000
21001 if let Some(ValueKind::Time(def)) = declared_default {
21002 if let Some(min) = minimum {
21003 let min_sem = semantics::time_to_semantic(min);
21004 if semantics::compare_semantic_times(def, &min_sem) == Ordering::Less {
21005 errors.push(Error::validation_with_context(
21006 format!(
21007 "Type '{}' default time {} is before minimum {}",
21008 type_name, def, min
21009 ),
21010 Some(source.clone()),
21011 None::<String>,
21012 spec_context,
21013 None,
21014 ));
21015 }
21016 }
21017 if let Some(max) = maximum {
21018 let max_sem = semantics::time_to_semantic(max);
21019 if semantics::compare_semantic_times(def, &max_sem) == Ordering::Greater {
21020 errors.push(Error::validation_with_context(
21021 format!(
21022 "Type '{}' default time {} is after maximum {}",
21023 type_name, def, max
21024 ),
21025 Some(source.clone()),
21026 None::<String>,
21027 spec_context,
21028 None,
21029 ));
21030 }
21031 }
21032 }
21033 }
21034
21035 TypeSpecification::NumberRange { .. }
21036 | TypeSpecification::DateRange { .. }
21037 | TypeSpecification::TimeRange { .. }
21038 | TypeSpecification::MeasureRange { .. }
21039 | TypeSpecification::RatioRange { .. } => {
21040 if let Err(message) = semantics::check_range_bound_consistency(specs, unit_index) {
21041 errors.push(Error::validation_with_context(
21042 format!("Type '{}' has {message}", type_name),
21043 Some(source.clone()),
21044 None::<String>,
21045 spec_context,
21046 None,
21047 ));
21048 }
21049 }
21050 TypeSpecification::Boolean { .. } => {
21051 }
21053 TypeSpecification::Veto { .. } => {
21054 }
21057 TypeSpecification::Undetermined => unreachable!(
21058 "BUG: validate_type_specification_constraints called with Undetermined sentinel type; this type exists only during type inference"
21059 ),
21060 TypeSpecification::Spec { .. } | TypeSpecification::List { .. } => {}
21061 }
21062
21063 errors
21064}