1use crate::error::LemmaError;
10use crate::parsing::ast::Span;
11use crate::semantic::{FactReference, LemmaType, TypeDef, TypeSpecification};
12use std::collections::{HashMap, HashSet};
13use std::sync::Arc;
14
15#[derive(Debug)]
18pub struct ResolvedDocumentTypes {
19 pub named_types: HashMap<String, LemmaType>,
21
22 pub inline_type_definitions: HashMap<FactReference, LemmaType>,
24
25 pub unit_index: HashMap<String, LemmaType>,
28}
29
30#[derive(Debug, Clone)]
35pub struct TypeRegistry {
36 named_types: HashMap<String, HashMap<String, TypeDef>>,
39 inline_type_definitions: HashMap<String, HashMap<FactReference, TypeDef>>,
42}
43
44impl TypeRegistry {
45 pub fn new() -> Self {
47 TypeRegistry {
48 named_types: HashMap::new(),
49 inline_type_definitions: HashMap::new(),
50 }
51 }
52
53 pub fn register_type(&mut self, doc: &str, def: TypeDef) -> Result<(), LemmaError> {
55 let def_loc = def.source_location().clone();
56 match &def {
57 TypeDef::Regular { name, .. } | TypeDef::Import { name, .. } => {
58 let doc_types = self.named_types.entry(doc.to_string()).or_default();
60
61 if doc_types.contains_key(name) {
63 return Err(LemmaError::engine(
64 format!("Type '{}' is already defined in document '{}'", name, doc),
65 def_loc.span.clone(),
66 &def_loc.attribute,
67 Arc::from(""),
68 &def_loc.doc_name,
69 1,
70 None::<String>,
71 ));
72 }
73
74 doc_types.insert(name.clone(), def);
76 }
77 TypeDef::Inline { fact_ref, .. } => {
78 let doc_inline_types = self
80 .inline_type_definitions
81 .entry(doc.to_string())
82 .or_default();
83
84 if doc_inline_types.contains_key(fact_ref) {
86 return Err(LemmaError::engine(
87 format!(
88 "Inline type definition for fact '{}' is already defined in document '{}'",
89 fact_ref.fact, doc
90 ),
91 def_loc.span.clone(),
92 &def_loc.attribute,
93 Arc::from(""),
94 &def_loc.doc_name,
95 1,
96 None::<String>,
97 ));
98 }
99
100 doc_inline_types.insert(fact_ref.clone(), def);
102 }
103 }
104 Ok(())
105 }
106
107 pub fn resolve_types(&self, doc: &str) -> Result<ResolvedDocumentTypes, LemmaError> {
117 self.resolve_types_internal(doc, true)
118 }
119
120 pub fn resolve_named_types(&self, doc: &str) -> Result<ResolvedDocumentTypes, LemmaError> {
122 self.resolve_types_internal(doc, false)
123 }
124
125 fn resolve_types_internal(
126 &self,
127 doc: &str,
128 include_anonymous: bool,
129 ) -> Result<ResolvedDocumentTypes, LemmaError> {
130 let mut named_types = HashMap::new();
131 let mut inline_type_definitions = HashMap::new();
132 let mut visited = HashSet::new();
133
134 if let Some(doc_types) = self.named_types.get(doc) {
136 for type_name in doc_types.keys() {
137 match self.resolve_type_internal(doc, type_name, &mut visited)? {
138 Some(resolved_type) => {
139 named_types.insert(type_name.clone(), resolved_type);
140 }
141 None => {
142 unreachable!(
143 "BUG: registered named type '{}' could not be resolved (doc='{}')",
144 type_name, doc
145 );
146 }
147 }
148 visited.clear();
149 }
150 }
151
152 if include_anonymous {
154 if let Some(doc_inline_types) = self.inline_type_definitions.get(doc) {
155 for (fact_ref, type_def) in doc_inline_types {
156 let mut visited = HashSet::new();
157 match self.resolve_inline_type_definition(
158 doc,
159 fact_ref,
160 type_def,
161 &mut visited,
162 )? {
163 Some(resolved_type) => {
164 inline_type_definitions.insert(fact_ref.clone(), resolved_type);
165 }
166 None => {
167 unreachable!(
168 "BUG: registered inline type definition for fact '{}' could not be resolved (doc='{}')",
169 fact_ref, doc
170 );
171 }
172 }
173 }
174 }
175 }
176
177 let mut unit_index: HashMap<String, LemmaType> = HashMap::new();
179 let mut errors = Vec::new();
180
181 for resolved_type in named_types.values() {
183 if let Err(e) = self.add_units_to_index(&mut unit_index, resolved_type, doc, || {
184 resolved_type
185 .name
186 .as_deref()
187 .unwrap_or("inline")
188 .to_string()
189 }) {
190 errors.push(e);
191 }
192 }
193
194 for (fact_ref, resolved_type) in &inline_type_definitions {
196 if let Err(e) = self.add_units_to_index(&mut unit_index, resolved_type, doc, || {
197 format!("{}::{}", doc, fact_ref)
198 }) {
199 errors.push(e);
200 }
201 }
202
203 if !errors.is_empty() {
205 let combined_message = errors
208 .iter()
209 .map(|e| match e {
210 LemmaError::Engine(details) => details.message.clone(),
211 LemmaError::CircularDependency { details, .. } => details.message.clone(),
212 LemmaError::Parse(details) => details.message.clone(),
213 LemmaError::Semantic(details) => details.message.clone(),
214 LemmaError::Inversion(details) => details.message.clone(),
215 LemmaError::Runtime(details) => details.message.clone(),
216 LemmaError::MissingFact(details) => details.message.clone(),
217 LemmaError::ResourceLimitExceeded {
218 limit_name,
219 limit_value,
220 actual_value,
221 suggestion,
222 } => {
223 format!(
224 "Resource limit exceeded: {} (limit: {}, actual: {}). {}",
225 limit_name, limit_value, actual_value, suggestion
226 )
227 }
228 LemmaError::MultipleErrors(errs) => errs
229 .iter()
230 .map(|e| match e {
231 LemmaError::Engine(details) => details.message.clone(),
232 LemmaError::CircularDependency { details, .. } => {
233 details.message.clone()
234 }
235 LemmaError::Parse(details) => details.message.clone(),
236 LemmaError::Semantic(details) => details.message.clone(),
237 LemmaError::Inversion(details) => details.message.clone(),
238 LemmaError::Runtime(details) => details.message.clone(),
239 LemmaError::MissingFact(details) => details.message.clone(),
240 LemmaError::ResourceLimitExceeded {
241 limit_name,
242 limit_value,
243 actual_value,
244 suggestion,
245 } => {
246 format!(
247 "Resource limit exceeded: {} (limit: {}, actual: {}). {}",
248 limit_name, limit_value, actual_value, suggestion
249 )
250 }
251 LemmaError::MultipleErrors(_) => "Multiple errors".to_string(),
252 })
253 .collect::<Vec<_>>()
254 .join("; "),
255 })
256 .collect::<Vec<_>>()
257 .join("; ");
258 return Err(LemmaError::engine(
259 &combined_message,
260 Span {
261 start: 0,
262 end: 0,
263 line: 1,
264 col: 0,
265 },
266 "<internal>",
267 Arc::from(""),
268 doc,
269 1,
270 None::<String>,
271 ));
272 }
273
274 Ok(ResolvedDocumentTypes {
275 named_types,
276 inline_type_definitions,
277 unit_index,
278 })
279 }
280
281 fn resolve_type_internal(
283 &self,
284 doc: &str,
285 name: &str,
286 visited: &mut HashSet<String>,
287 ) -> Result<Option<LemmaType>, LemmaError> {
288 let key = format!("{}::{}", doc, name);
290 if visited.contains(&key) {
291 return Err(LemmaError::circular_dependency(
292 format!("Circular dependency detected in type resolution: {}", key),
293 crate::parsing::ast::Span {
294 start: 0,
295 end: 0,
296 line: 1,
297 col: 0,
298 },
299 "<internal>",
300 std::sync::Arc::from(""),
301 doc,
302 1,
303 vec![],
304 None::<String>,
305 ));
306 }
307 visited.insert(key.clone());
308
309 let type_def = match self.named_types.get(doc).and_then(|dt| dt.get(name)) {
311 Some(def) => def.clone(),
312 None => {
313 visited.remove(&key);
314 return Ok(None);
315 }
316 };
317
318 let (parent, from, overrides, type_name) = match &type_def {
320 TypeDef::Regular {
321 name,
322 parent,
323 overrides,
324 ..
325 } => (parent.clone(), None, overrides.clone(), name.clone()),
326 TypeDef::Import {
327 name,
328 source_type,
329 from,
330 overrides,
331 ..
332 } => (
333 source_type.clone(),
334 Some(from.clone()),
335 overrides.clone(),
336 name.clone(),
337 ),
338 TypeDef::Inline { .. } => {
339 visited.remove(&key);
341 return Ok(None);
342 }
343 };
344
345 let parent_specs = match self.resolve_parent(
346 doc,
347 &parent,
348 &from,
349 visited,
350 type_def.source_location(),
351 ) {
352 Ok(Some(specs)) => specs,
353 Ok(None) => {
354 visited.remove(&key);
357 return Err(LemmaError::engine(
358 format!("Unknown type: '{}'. Type must be defined before use. Valid standard types are: boolean, scale, number, ratio, text, date, time, duration, percent", parent),
359 Span { start: 0, end: 0, line: 1, col: 0 },
360 "<internal>",
361 Arc::from(""),
362 doc,
363 1,
364 None::<String>,
365 ));
366 }
367 Err(e) => {
368 visited.remove(&key);
369 return Err(e);
370 }
371 };
372
373 let final_specs = if let Some(overrides) = &overrides {
375 match self.apply_overrides(parent_specs, overrides, type_def.source_location()) {
376 Ok(specs) => specs,
377 Err(errors) => {
378 visited.remove(&key);
379 let combined_message = errors
382 .iter()
383 .map(|e| match e {
384 LemmaError::Engine(details) => details.message.clone(),
385 LemmaError::CircularDependency { details, .. } => details.message.clone(),
386 LemmaError::Parse(details) => details.message.clone(),
387 LemmaError::Semantic(details) => details.message.clone(),
388 LemmaError::Inversion(details) => details.message.clone(),
389 LemmaError::Runtime(details) => details.message.clone(),
390 LemmaError::MissingFact(details) => details.message.clone(),
391 LemmaError::ResourceLimitExceeded { limit_name, limit_value, actual_value, suggestion } => {
392 format!("Resource limit exceeded: {} (limit: {}, actual: {}). {}", limit_name, limit_value, actual_value, suggestion)
393 },
394 LemmaError::MultipleErrors(errs) => {
395 errs.iter().map(|e| match e {
396 LemmaError::Engine(details) => details.message.clone(),
397 LemmaError::CircularDependency { details, .. } => details.message.clone(),
398 LemmaError::Parse(details) => details.message.clone(),
399 LemmaError::Semantic(details) => details.message.clone(),
400 LemmaError::Inversion(details) => details.message.clone(),
401 LemmaError::Runtime(details) => details.message.clone(),
402 LemmaError::MissingFact(details) => details.message.clone(),
403 LemmaError::ResourceLimitExceeded { limit_name, limit_value, actual_value, suggestion } => {
404 format!("Resource limit exceeded: {} (limit: {}, actual: {}). {}", limit_name, limit_value, actual_value, suggestion)
405 },
406 LemmaError::MultipleErrors(_) => "Multiple errors".to_string(),
407 }).collect::<Vec<_>>().join("; ")
408 },
409 })
410 .collect::<Vec<_>>()
411 .join("; ");
412 return Err(LemmaError::engine(
413 &combined_message,
414 Span {
415 start: 0,
416 end: 0,
417 line: 1,
418 col: 0,
419 },
420 &type_def.source_location().attribute,
421 Arc::from(""),
422 doc,
423 1,
424 None::<String>,
425 ));
426 }
427 }
428 } else {
429 parent_specs
430 };
431
432 visited.remove(&key);
433
434 Ok(Some(LemmaType {
435 name: Some(type_name),
436 specifications: final_specs,
437 }))
438 }
439
440 fn resolve_parent(
442 &self,
443 doc: &str,
444 parent: &str,
445 from: &Option<String>,
446 visited: &mut HashSet<String>,
447 source: &crate::Source,
448 ) -> Result<Option<TypeSpecification>, LemmaError> {
449 if let Some(specs) = self.resolve_standard_type(parent) {
451 return Ok(Some(specs));
452 }
453
454 let parent_doc = from.as_deref().unwrap_or(doc);
456 match self.resolve_type_internal(parent_doc, parent, visited) {
457 Ok(Some(t)) => Ok(Some(t.specifications)),
458 Ok(None) => {
459 let type_exists = if let Some(doc_types) = self.named_types.get(parent_doc) {
461 doc_types.contains_key(parent)
462 } else {
463 false
464 };
465
466 if !type_exists {
467 Err(LemmaError::engine(
469 format!("Unknown type: '{}'. Type must be defined before use. Valid standard types are: boolean, scale, number, ratio, text, date, time, duration, percent", parent),
470 source.span.clone(),
471 &source.attribute,
472 Arc::from(""),
473 &source.doc_name,
474 1,
475 None::<String>,
476 ))
477 } else {
478 Ok(None)
481 }
482 }
483 Err(e) => Err(e),
484 }
485 }
486
487 pub fn resolve_standard_type(&self, name: &str) -> Option<TypeSpecification> {
489 match name {
490 "boolean" => Some(TypeSpecification::boolean()),
491 "scale" => Some(TypeSpecification::scale()),
492 "number" => Some(TypeSpecification::number()),
493 "ratio" => Some(TypeSpecification::ratio()),
494 "text" => Some(TypeSpecification::text()),
495 "date" => Some(TypeSpecification::date()),
496 "time" => Some(TypeSpecification::time()),
497 "duration" => Some(TypeSpecification::duration()),
498 "percent" => Some(TypeSpecification::ratio()),
499 _ => None,
500 }
501 }
502
503 pub fn find_document_for_fact(&self, fact_ref: &FactReference) -> Option<String> {
505 for (doc_name, inline_types) in &self.inline_type_definitions {
506 if inline_types.contains_key(fact_ref) {
507 return Some(doc_name.clone());
508 }
509 }
510 None
511 }
512
513 fn apply_overrides(
515 &self,
516 mut specs: TypeSpecification,
517 overrides: &[(String, Vec<String>)],
518 source: &crate::Source,
519 ) -> Result<TypeSpecification, Vec<LemmaError>> {
520 let mut existing_units: Vec<String> = match &specs {
522 TypeSpecification::Scale { units, .. } => {
523 units.iter().map(|u| u.name.clone()).collect()
524 }
525 TypeSpecification::Ratio { units, .. } => {
526 units.iter().map(|u| u.name.clone()).collect()
527 }
528 _ => Vec::new(),
529 };
530
531 let mut errors = Vec::new();
532 let mut valid_overrides = Vec::new();
533
534 for (command, args) in overrides {
536 if command == "unit" && !args.is_empty() {
537 let unit_name = &args[0];
538 if existing_units.iter().any(|u| u == unit_name) {
539 errors.push(LemmaError::engine(
540 format!("Unit '{}' already exists in parent type. Use a different unit name (e.g., 'my_{}') if you need another unit factor.", unit_name, unit_name),
541 source.span.clone(),
542 &source.attribute,
543 Arc::from(""),
544 &source.doc_name,
545 1,
546 None::<String>,
547 ));
548 } else {
550 existing_units.push(unit_name.clone());
551 valid_overrides.push((command.clone(), args.clone()));
552 }
553 } else {
554 valid_overrides.push((command.clone(), args.clone()));
556 }
557 }
558
559 for (command, args) in valid_overrides {
563 let specs_clone = specs.clone();
565 match specs.apply_override(&command, &args) {
566 Ok(updated_specs) => {
567 specs = updated_specs;
568 }
569 Err(e) => {
570 errors.push(LemmaError::engine(
571 format!("Failed to apply override '{}': {}", command, e),
572 source.span.clone(),
573 &source.attribute,
574 Arc::from(""),
575 &source.doc_name,
576 1,
577 None::<String>,
578 ));
579 specs = specs_clone;
581 }
582 }
583 }
584
585 if !errors.is_empty() {
586 return Err(errors);
587 }
588
589 Ok(specs)
590 }
591
592 fn resolve_inline_type_definition(
594 &self,
595 doc: &str,
596 _fact_ref: &FactReference,
597 type_def: &TypeDef,
598 visited: &mut HashSet<String>,
599 ) -> Result<Option<LemmaType>, LemmaError> {
600 let def_loc = type_def.source_location().clone();
601 let TypeDef::Inline {
602 parent,
603 overrides,
604 fact_ref: _,
605 from,
606 ..
607 } = type_def
608 else {
609 return Ok(None);
610 };
611
612 let parent_specs = match self.resolve_parent(doc, parent, from, visited, &def_loc) {
613 Ok(Some(specs)) => specs,
614 Ok(None) => {
615 return Err(LemmaError::engine(
618 format!("Unknown type: '{}'. Type must be defined before use. Valid standard types are: boolean, scale, number, ratio, text, date, time, duration, percent", parent),
619 def_loc.span.clone(),
620 &def_loc.attribute,
621 Arc::from(""),
622 &def_loc.doc_name,
623 1,
624 None::<String>,
625 ));
626 }
627 Err(e) => return Err(e),
628 };
629
630 let final_specs = if let Some(overrides) = overrides {
631 match self.apply_overrides(parent_specs, overrides, &def_loc) {
632 Ok(specs) => specs,
633 Err(errors) => {
634 let combined_message = errors
637 .iter()
638 .map(|e| match e {
639 LemmaError::Engine(details) => details.message.clone(),
640 LemmaError::CircularDependency { details, .. } => details.message.clone(),
641 LemmaError::Parse(details) => details.message.clone(),
642 LemmaError::Semantic(details) => details.message.clone(),
643 LemmaError::Inversion(details) => details.message.clone(),
644 LemmaError::Runtime(details) => details.message.clone(),
645 LemmaError::MissingFact(details) => details.message.clone(),
646 LemmaError::ResourceLimitExceeded { limit_name, limit_value, actual_value, suggestion } => {
647 format!("Resource limit exceeded: {} (limit: {}, actual: {}). {}", limit_name, limit_value, actual_value, suggestion)
648 },
649 LemmaError::MultipleErrors(errs) => {
650 errs.iter().map(|e| match e {
651 LemmaError::Engine(details) => details.message.clone(),
652 LemmaError::CircularDependency { details, .. } => details.message.clone(),
653 LemmaError::Parse(details) => details.message.clone(),
654 LemmaError::Semantic(details) => details.message.clone(),
655 LemmaError::Inversion(details) => details.message.clone(),
656 LemmaError::Runtime(details) => details.message.clone(),
657 LemmaError::MissingFact(details) => details.message.clone(),
658 LemmaError::ResourceLimitExceeded { limit_name, limit_value, actual_value, suggestion } => {
659 format!("Resource limit exceeded: {} (limit: {}, actual: {}). {}", limit_name, limit_value, actual_value, suggestion)
660 },
661 LemmaError::MultipleErrors(_) => "Multiple errors".to_string(),
662 }).collect::<Vec<_>>().join("; ")
663 },
664 })
665 .collect::<Vec<_>>()
666 .join("; ");
667 return Err(LemmaError::engine(
668 &combined_message,
669 Span {
670 start: 0,
671 end: 0,
672 line: 1,
673 col: 0,
674 },
675 &def_loc.attribute,
676 Arc::from(""),
677 &def_loc.doc_name,
678 1,
679 None::<String>,
680 ));
681 }
682 }
683 } else {
684 parent_specs
685 };
686
687 Ok(Some(LemmaType::without_name(final_specs)))
688 }
689
690 pub fn get_all_units(&self) -> HashMap<String, Vec<(String, String)>> {
700 let mut unit_map: HashMap<String, Vec<(String, String)>> = HashMap::new();
701 let mut visited = HashSet::new();
702
703 for (doc_name, doc_types) in &self.named_types {
705 for type_name in doc_types.keys() {
706 if let Ok(Some(resolved_type)) =
707 self.resolve_type_internal(doc_name, type_name, &mut visited)
708 {
709 let units = self.extract_units_from_specs(&resolved_type.specifications);
710 for unit in units {
711 unit_map
712 .entry(unit)
713 .or_default()
714 .push((doc_name.clone(), type_name.clone()));
715 }
716 }
717 visited.clear();
718 }
719 }
720
721 for (doc_name, inline_types) in &self.inline_type_definitions {
723 for (fact_ref, type_def) in inline_types {
724 if let Ok(Some(resolved_type)) = self.resolve_inline_type_definition(
725 doc_name,
726 fact_ref,
727 type_def,
728 &mut HashSet::new(),
729 ) {
730 let units = self.extract_units_from_specs(&resolved_type.specifications);
731 let type_name = format!("{}::{}", doc_name, fact_ref);
732 for unit in units {
733 unit_map
734 .entry(unit)
735 .or_default()
736 .push((doc_name.clone(), type_name.clone()));
737 }
738 }
739 }
740 }
741
742 unit_map
743 }
744
745 fn add_units_to_index<F>(
748 &self,
749 unit_index: &mut HashMap<String, LemmaType>,
750 resolved_type: &LemmaType,
751 doc: &str,
752 get_type_name: F,
753 ) -> Result<(), LemmaError>
754 where
755 F: FnOnce() -> String,
756 {
757 let units = self.extract_units_from_specs(&resolved_type.specifications);
758 let current_name = get_type_name(); for unit in units {
760 if let Some(existing_type) = unit_index.get(&unit) {
761 let existing_name = existing_type.name.as_deref().unwrap_or("inline");
762
763 let same_type = existing_type.name.as_deref() == resolved_type.name.as_deref();
766 if same_type {
767 continue;
769 }
770
771 let might_be_inheritance =
776 match (&existing_type.specifications, &resolved_type.specifications) {
777 (
778 TypeSpecification::Scale {
779 units: existing_units,
780 ..
781 },
782 TypeSpecification::Scale {
783 units: current_units,
784 ..
785 },
786 ) => {
787 let current_extends_existing = existing_units.len()
789 < current_units.len()
790 && existing_units
791 .iter()
792 .all(|eu| current_units.iter().any(|cu| cu.name == eu.name));
793 let existing_extends_current = current_units.len()
795 < existing_units.len()
796 && current_units
797 .iter()
798 .all(|cu| existing_units.iter().any(|eu| eu.name == cu.name));
799 current_extends_existing || existing_extends_current
800 }
801 (
802 TypeSpecification::Ratio {
803 units: existing_units,
804 ..
805 },
806 TypeSpecification::Ratio {
807 units: current_units,
808 ..
809 },
810 ) => {
811 let current_extends_existing = existing_units.len()
813 < current_units.len()
814 && existing_units
815 .iter()
816 .all(|eu| current_units.iter().any(|cu| cu.name == eu.name));
817 let existing_extends_current = current_units.len()
819 < existing_units.len()
820 && current_units
821 .iter()
822 .all(|cu| existing_units.iter().any(|eu| eu.name == cu.name));
823 current_extends_existing || existing_extends_current
824 }
825 _ => false,
826 };
827
828 if might_be_inheritance {
829 continue;
831 }
832
833 let source = self
834 .named_types
835 .get(doc)
836 .and_then(|defs| defs.get(¤t_name))
837 .map(|def| def.source_location());
838
839 return Err(LemmaError::engine(
840 format!("Ambiguous unit '{}' in document '{}'. Defined in multiple types: {} and {}", unit, doc, existing_name, current_name),
841 source
842 .map(|s| s.span.clone())
843 .unwrap_or(Span {
844 start: 0,
845 end: 0,
846 line: 1,
847 col: 0,
848 }),
849 source.map(|s| s.attribute.as_str()).unwrap_or("<input>"),
850 Arc::from(""),
851 source.map(|s| s.doc_name.as_str()).unwrap_or(doc),
852 1,
853 None::<String>,
854 ));
855 }
856 unit_index.insert(unit, resolved_type.clone());
857 }
858 Ok(())
859 }
860
861 fn extract_units_from_specs(&self, specs: &TypeSpecification) -> Vec<String> {
864 match specs {
865 TypeSpecification::Scale { units, .. } => {
866 units.iter().map(|unit| unit.name.clone()).collect()
867 }
868 TypeSpecification::Ratio { units, .. } => {
869 units.iter().map(|unit| unit.name.clone()).collect()
870 }
871 _ => Vec::new(),
872 }
873 }
874}
875
876impl Default for TypeRegistry {
877 fn default() -> Self {
878 Self::new()
879 }
880}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885 use crate::parse;
886 use crate::ResourceLimits;
887 use rust_decimal::Decimal;
888 use std::str::FromStr;
889
890 #[test]
891 fn test_registry_creation() {
892 let registry = TypeRegistry::new();
893 assert!(registry.named_types.is_empty());
894 assert!(registry.inline_type_definitions.is_empty());
895 }
896
897 #[test]
898 fn test_resolve_standard_types() {
899 let registry = TypeRegistry::new();
900
901 assert!(registry.resolve_standard_type("boolean").is_some());
902 assert!(registry.resolve_standard_type("scale").is_some());
903 assert!(registry.resolve_standard_type("number").is_some());
904 assert!(registry.resolve_standard_type("ratio").is_some());
905 assert!(registry.resolve_standard_type("text").is_some());
906 assert!(registry.resolve_standard_type("date").is_some());
907 assert!(registry.resolve_standard_type("time").is_some());
908 assert!(registry.resolve_standard_type("duration").is_some());
909 assert!(registry.resolve_standard_type("unknown").is_none());
910 }
911
912 #[test]
913 fn test_register_named_type() {
914 let mut registry = TypeRegistry::new();
915 let type_def = TypeDef::Regular {
916 source_location: crate::Source::new(
917 "<test>",
918 crate::parsing::ast::Span {
919 start: 0,
920 end: 0,
921 line: 1,
922 col: 0,
923 },
924 "test_doc",
925 ),
926 name: "money".to_string(),
927 parent: "number".to_string(),
928 overrides: None,
929 };
930
931 let result = registry.register_type("test_doc", type_def);
932 assert!(result.is_ok());
933 }
934
935 #[test]
936 fn test_register_inline_type_definition() {
937 use crate::semantic::FactReference;
938 let mut registry = TypeRegistry::new();
939 let fact_ref = FactReference::local("age".to_string());
940 let type_def = TypeDef::Inline {
941 source_location: crate::Source::new(
942 "<test>",
943 crate::parsing::ast::Span {
944 start: 0,
945 end: 0,
946 line: 1,
947 col: 0,
948 },
949 "test_doc",
950 ),
951 parent: "number".to_string(),
952 overrides: Some(vec![
953 ("minimum".to_string(), vec!["0".to_string()]),
954 ("maximum".to_string(), vec!["150".to_string()]),
955 ]),
956 fact_ref: fact_ref.clone(),
957 from: None,
958 };
959
960 let result = registry.register_type("test_doc", type_def);
961 assert!(result.is_ok());
962
963 assert!(registry
965 .inline_type_definitions
966 .get("test_doc")
967 .unwrap()
968 .contains_key(&fact_ref));
969 }
970
971 #[test]
972 fn test_register_duplicate_type_fails() {
973 let mut registry = TypeRegistry::new();
974 let type_def = TypeDef::Regular {
975 source_location: crate::Source::new(
976 "<test>",
977 crate::parsing::ast::Span {
978 start: 0,
979 end: 0,
980 line: 1,
981 col: 0,
982 },
983 "test_doc",
984 ),
985 name: "money".to_string(),
986 parent: "number".to_string(),
987 overrides: None,
988 };
989
990 registry
991 .register_type("test_doc", type_def.clone())
992 .unwrap();
993 let result = registry.register_type("test_doc", type_def);
994 assert!(result.is_err());
995 }
996
997 #[test]
998 fn test_resolve_custom_type_from_standard() {
999 let mut registry = TypeRegistry::new();
1000 let type_def = TypeDef::Regular {
1001 source_location: crate::Source::new(
1002 "<test>",
1003 crate::parsing::ast::Span {
1004 start: 0,
1005 end: 0,
1006 line: 1,
1007 col: 0,
1008 },
1009 "test_doc",
1010 ),
1011 name: "money".to_string(),
1012 parent: "number".to_string(),
1013 overrides: None,
1014 };
1015
1016 registry.register_type("test_doc", type_def).unwrap();
1017 let resolved = registry.resolve_types("test_doc").unwrap();
1018
1019 assert!(resolved.named_types.contains_key("money"));
1020 let money_type = resolved.named_types.get("money").unwrap();
1021 assert_eq!(money_type.name, Some("money".to_string()));
1022 }
1023
1024 #[test]
1025 fn test_type_definition_resolution() {
1026 let code = r#"doc test
1027type dice = number -> minimum 0 -> maximum 6"#;
1028
1029 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1030 let doc = &docs[0];
1031
1032 let mut registry = TypeRegistry::new();
1034 registry
1035 .register_type(&doc.name, doc.types[0].clone())
1036 .unwrap();
1037
1038 let resolved_types = registry.resolve_types(&doc.name).unwrap();
1039 let dice_type = resolved_types.named_types.get("dice").unwrap();
1040
1041 match &dice_type.specifications {
1043 TypeSpecification::Number {
1044 minimum, maximum, ..
1045 } => {
1046 assert_eq!(*minimum, Some(Decimal::from(0)));
1047 assert_eq!(*maximum, Some(Decimal::from(6)));
1048 }
1049 _ => panic!("Expected Number type specifications"),
1050 }
1051 }
1052
1053 #[test]
1054 fn test_type_definition_with_multiple_commands() {
1055 let code = r#"doc test
1056type money = scale -> decimals 2 -> unit eur 1.0 -> unit usd 1.18"#;
1057
1058 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1059 let doc = &docs[0];
1060 let type_def = &doc.types[0];
1061
1062 let mut registry = TypeRegistry::new();
1064 registry.register_type(&doc.name, type_def.clone()).unwrap();
1065
1066 let resolved_types = registry.resolve_types(&doc.name).unwrap();
1067 let money_type = resolved_types.named_types.get("money").unwrap();
1068
1069 match &money_type.specifications {
1070 TypeSpecification::Scale {
1071 decimals, units, ..
1072 } => {
1073 assert_eq!(*decimals, Some(2));
1074 assert_eq!(units.len(), 2);
1075 assert!(units.iter().any(|u| u.name == "eur"));
1076 assert!(units.iter().any(|u| u.name == "usd"));
1077 }
1078 _ => panic!("Expected Scale type specifications"),
1079 }
1080 }
1081
1082 #[test]
1083 fn test_number_type_with_decimals() {
1084 let code = r#"doc test
1085type price = number -> decimals 2 -> minimum 0"#;
1086
1087 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1088 let doc = &docs[0];
1089
1090 let mut registry = TypeRegistry::new();
1092 registry
1093 .register_type(&doc.name, doc.types[0].clone())
1094 .unwrap();
1095
1096 let resolved_types = registry.resolve_types(&doc.name).unwrap();
1097 let price_type = resolved_types.named_types.get("price").unwrap();
1098
1099 match &price_type.specifications {
1101 TypeSpecification::Number {
1102 decimals, minimum, ..
1103 } => {
1104 assert_eq!(*decimals, Some(2));
1105 assert_eq!(*minimum, Some(Decimal::from(0)));
1106 }
1107 _ => panic!("Expected Number type specifications with decimals"),
1108 }
1109 }
1110
1111 #[test]
1112 fn test_number_type_decimals_only() {
1113 let code = r#"doc test
1114type precise_number = number -> decimals 4"#;
1115
1116 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1117 let doc = &docs[0];
1118
1119 let mut registry = TypeRegistry::new();
1120 registry
1121 .register_type(&doc.name, doc.types[0].clone())
1122 .unwrap();
1123
1124 let resolved_types = registry.resolve_types(&doc.name).unwrap();
1125 let precise_type = resolved_types.named_types.get("precise_number").unwrap();
1126
1127 match &precise_type.specifications {
1128 TypeSpecification::Number { decimals, .. } => {
1129 assert_eq!(*decimals, Some(4));
1130 }
1131 _ => panic!("Expected Number type with decimals 4"),
1132 }
1133 }
1134
1135 #[test]
1136 fn test_scale_type_decimals_only() {
1137 let code = r#"doc test
1138type weight = scale -> decimals 3"#;
1139
1140 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1141 let doc = &docs[0];
1142
1143 let mut registry = TypeRegistry::new();
1144 registry
1145 .register_type(&doc.name, doc.types[0].clone())
1146 .unwrap();
1147
1148 let resolved_types = registry.resolve_types(&doc.name).unwrap();
1149 let weight_type = resolved_types.named_types.get("weight").unwrap();
1150
1151 match &weight_type.specifications {
1152 TypeSpecification::Scale { decimals, .. } => {
1153 assert_eq!(*decimals, Some(3));
1154 }
1155 _ => panic!("Expected Scale type with decimals 3"),
1156 }
1157 }
1158
1159 #[test]
1160 fn test_ratio_type_rejects_decimals_command() {
1161 let code = r#"doc test
1162type ratio_type = ratio -> decimals 2"#;
1163
1164 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1165 let doc = &docs[0];
1166
1167 let mut registry = TypeRegistry::new();
1168 registry
1170 .register_type(&doc.name, doc.types[0].clone())
1171 .unwrap();
1172
1173 let result = registry.resolve_types(&doc.name);
1175
1176 assert!(
1178 result.is_err(),
1179 "Ratio type should reject decimals command during resolution"
1180 );
1181 let error_msg = result.unwrap_err().to_string();
1182 assert!(
1183 error_msg.contains("decimals") || error_msg.contains("Invalid command"),
1184 "Error message should mention decimals or invalid command, got: {}",
1185 error_msg
1186 );
1187 }
1188
1189 #[test]
1190 fn test_ratio_type_with_default_command() {
1191 let code = r#"doc test
1192type percentage = ratio -> minimum 0 -> maximum 1 -> default 0.5"#;
1193
1194 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1195 let doc = &docs[0];
1196
1197 let mut registry = TypeRegistry::new();
1198 registry
1199 .register_type(&doc.name, doc.types[0].clone())
1200 .unwrap();
1201
1202 let resolved_types = registry.resolve_types(&doc.name).unwrap();
1203 let percentage_type = resolved_types.named_types.get("percentage").unwrap();
1204
1205 match &percentage_type.specifications {
1206 TypeSpecification::Ratio {
1207 minimum,
1208 maximum,
1209 default,
1210 ..
1211 } => {
1212 assert_eq!(*minimum, Some(Decimal::from(0)));
1213 assert_eq!(*maximum, Some(Decimal::from(1)));
1214 assert_eq!(*default, Some(Decimal::from_str("0.5").unwrap()));
1215 }
1216 _ => panic!("Expected Ratio type specifications"),
1217 }
1218 }
1219
1220 #[test]
1221 fn test_invalid_parent_type_in_named_type_should_error() {
1222 let code = r#"doc test
1223type invalid = nonexistent_type -> minimum 0"#;
1224
1225 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1226 let doc = &docs[0];
1227
1228 let mut registry = TypeRegistry::new();
1229 registry
1230 .register_type(&doc.name, doc.types[0].clone())
1231 .unwrap();
1232
1233 let result = registry.resolve_types(&doc.name);
1234 assert!(result.is_err(), "Should reject invalid parent type");
1235
1236 let error_msg = result.unwrap_err().to_string();
1237 assert!(
1238 error_msg.contains("Unknown type") && error_msg.contains("nonexistent_type"),
1239 "Error should mention unknown type. Got: {}",
1240 error_msg
1241 );
1242 }
1243
1244 #[test]
1245 fn test_invalid_standard_type_name_should_error() {
1246 let code = r#"doc test
1248type invalid = choice -> option "a""#;
1249
1250 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1251 let doc = &docs[0];
1252
1253 let mut registry = TypeRegistry::new();
1254 registry
1255 .register_type(&doc.name, doc.types[0].clone())
1256 .unwrap();
1257
1258 let result = registry.resolve_types(&doc.name);
1259 assert!(result.is_err(), "Should reject invalid type base 'choice'");
1260
1261 let error_msg = result.unwrap_err().to_string();
1262 assert!(
1263 error_msg.contains("Unknown type") && error_msg.contains("choice"),
1264 "Error should mention unknown type 'choice'. Got: {}",
1265 error_msg
1266 );
1267 }
1268
1269 #[test]
1270 fn test_unit_override_validation_errors_are_reported() {
1271 let code = r#"doc test
1273type money = scale
1274 -> unit eur 1.00
1275 -> unit usd 1.19
1276
1277type money2 = money
1278 -> unit eur 1.20
1279 -> unit usd 1.21
1280 -> unit gbp 1.30"#;
1281
1282 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1283 let doc = &docs[0];
1284
1285 let mut registry = TypeRegistry::new();
1286 for type_def in &doc.types {
1287 registry.register_type(&doc.name, type_def.clone()).unwrap();
1288 }
1289
1290 let result = registry.resolve_types(&doc.name);
1291 assert!(result.is_err(), "Expected unit override conflicts to error");
1292
1293 let error_msg = result.unwrap_err().to_string();
1294 assert!(
1295 error_msg.contains("eur") || error_msg.contains("usd"),
1296 "Error should mention the conflicting units. Got: {}",
1297 error_msg
1298 );
1299 }
1300
1301 #[test]
1302 fn test_document_level_unit_ambiguity_errors_are_reported() {
1303 let code = r#"doc test
1305type money_a = scale
1306 -> unit eur 1.00
1307 -> unit usd 1.19
1308
1309type money_b = scale
1310 -> unit eur 1.00
1311 -> unit usd 1.20
1312
1313type length_a = scale
1314 -> unit meter 1.0
1315
1316type length_b = scale
1317 -> unit meter 1.0"#;
1318
1319 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1320 let doc = &docs[0];
1321
1322 let mut registry = TypeRegistry::new();
1323 for type_def in &doc.types {
1324 registry.register_type(&doc.name, type_def.clone()).unwrap();
1325 }
1326
1327 let result = registry.resolve_types(&doc.name);
1328 assert!(
1329 result.is_err(),
1330 "Expected ambiguous unit definitions to error"
1331 );
1332
1333 let error_msg = result.unwrap_err().to_string();
1334 assert!(
1335 error_msg.contains("eur") || error_msg.contains("usd") || error_msg.contains("meter"),
1336 "Error should mention at least one ambiguous unit. Got: {}",
1337 error_msg
1338 );
1339 }
1340
1341 #[test]
1342 fn test_number_type_cannot_have_units() {
1343 let code = r#"doc test
1344type price = number
1345 -> unit eur 1.00"#;
1346
1347 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1348 let doc = &docs[0];
1349
1350 let mut registry = TypeRegistry::new();
1351 registry
1352 .register_type(&doc.name, doc.types[0].clone())
1353 .unwrap();
1354
1355 let result = registry.resolve_types(&doc.name);
1356 assert!(result.is_err(), "Number types must reject unit commands");
1357
1358 let error_msg = result.unwrap_err().to_string();
1359 assert!(
1360 error_msg.contains("unit") && error_msg.contains("number"),
1361 "Error should mention units are invalid on number. Got: {}",
1362 error_msg
1363 );
1364 }
1365
1366 #[test]
1367 fn test_scale_type_can_have_units() {
1368 let code = r#"doc test
1369type money = scale
1370 -> unit eur 1.00
1371 -> unit usd 1.19"#;
1372
1373 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1374 let doc = &docs[0];
1375
1376 let mut registry = TypeRegistry::new();
1377 registry
1378 .register_type(&doc.name, doc.types[0].clone())
1379 .unwrap();
1380
1381 let resolved = registry.resolve_types(&doc.name).unwrap();
1382 let money_type = resolved.named_types.get("money").unwrap();
1383
1384 match &money_type.specifications {
1385 TypeSpecification::Scale { units, .. } => {
1386 assert_eq!(units.len(), 2);
1387 assert!(units.iter().any(|u| u.name == "eur"));
1388 assert!(units.iter().any(|u| u.name == "usd"));
1389 }
1390 other => panic!("Expected Scale type specifications, got {:?}", other),
1391 }
1392 }
1393
1394 #[test]
1395 fn test_extending_type_inherits_units() {
1396 let code = r#"doc test
1397type money = scale
1398 -> unit eur 1.00
1399 -> unit usd 1.19
1400
1401type my_money = money
1402 -> unit gbp 1.30"#;
1403
1404 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1405 let doc = &docs[0];
1406
1407 let mut registry = TypeRegistry::new();
1408 for type_def in &doc.types {
1409 registry.register_type(&doc.name, type_def.clone()).unwrap();
1410 }
1411
1412 let resolved = registry.resolve_types(&doc.name).unwrap();
1413 let my_money_type = resolved.named_types.get("my_money").unwrap();
1414
1415 match &my_money_type.specifications {
1416 TypeSpecification::Scale { units, .. } => {
1417 assert_eq!(units.len(), 3);
1418 assert!(units.iter().any(|u| u.name == "eur"));
1419 assert!(units.iter().any(|u| u.name == "usd"));
1420 assert!(units.iter().any(|u| u.name == "gbp"));
1421 }
1422 other => panic!("Expected Scale type specifications, got {:?}", other),
1423 }
1424 }
1425
1426 #[test]
1427 fn test_duplicate_unit_in_same_type_is_rejected() {
1428 let code = r#"doc test
1429type money = scale
1430 -> unit eur 1.00
1431 -> unit eur 1.19"#;
1432
1433 let docs = parse(code, "test.lemma", &ResourceLimits::default()).unwrap();
1434 let doc = &docs[0];
1435
1436 let mut registry = TypeRegistry::new();
1437 registry
1438 .register_type(&doc.name, doc.types[0].clone())
1439 .unwrap();
1440
1441 let result = registry.resolve_types(&doc.name);
1442 assert!(
1443 result.is_err(),
1444 "Duplicate units within a type should error"
1445 );
1446
1447 let error_msg = result.unwrap_err().to_string();
1448 assert!(
1449 error_msg.contains("Duplicate unit")
1450 || error_msg.contains("duplicate")
1451 || error_msg.contains("already exists")
1452 || error_msg.contains("eur"),
1453 "Error should mention duplicate unit issue. Got: {}",
1454 error_msg
1455 );
1456 }
1457}