1use crate::ast::{
2 ImportSpec, ImportStatement, Member, MonDocument, MonValue, MonValueKind,
3 SymbolTable as AstSymbolTable, TypeDef, TypeSpec,
4};
5use crate::error::{ResolverError, ValidationError};
6use miette::NamedSource;
7use std::collections::HashMap;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11pub struct Resolver {
12 resolved_documents: HashMap<PathBuf, MonDocument>,
14 resolving_stack: Vec<(PathBuf, Option<ImportStatement>)>,
16 pub symbol_table: AstSymbolTable,
18 pub anchors: HashMap<String, MonValue>,
20}
21
22impl Resolver {
23 #[must_use]
24 pub fn new() -> Self {
25 Resolver {
26 resolved_documents: HashMap::new(),
27 resolving_stack: Vec::new(), symbol_table: AstSymbolTable::new(),
29 anchors: HashMap::new(),
30 }
31 }
32
33 pub fn resolve(
34 &mut self,
35 document: MonDocument,
36 source_text: &str,
37 file_path: PathBuf,
38 causing_import: Option<ImportStatement>,
39 ) -> Result<MonDocument, ResolverError> {
40 if let Some((_, Some(existing_causing_import))) =
42 self.resolving_stack.iter().find(|(p, _)| p == &file_path)
43 {
44 let cycle_str = self
45 .resolving_stack
46 .iter()
47 .map(|(p, _)| p.to_string_lossy().to_string())
48 .collect::<Vec<String>>()
49 .join(" -> ");
50 return Err(ResolverError::CircularDependency {
51 cycle: format!("{} -> {}", cycle_str, file_path.to_string_lossy()),
52 src: NamedSource::new(file_path.to_string_lossy(), source_text.to_string()).into(),
53 span: (
54 existing_causing_import.pos_start,
55 existing_causing_import.pos_end - existing_causing_import.pos_start,
56 )
57 .into(),
58 });
59 }
60 self.resolving_stack
61 .push((file_path.clone(), causing_import)); let current_dir = file_path.parent().unwrap_or_else(|| Path::new("."));
65 let source_arc = Arc::new(source_text.to_string());
66
67 for import_statement in &document.imports {
68 let imported_path_str = import_statement.path.trim_matches('"');
69 let absolute_imported_path = current_dir.join(imported_path_str);
70
71 if self
73 .resolved_documents
74 .contains_key(&absolute_imported_path)
75 {
76 continue;
77 }
78
79 let imported_source_text =
81 std::fs::read_to_string(&absolute_imported_path).map_err(|_| {
82 ResolverError::ModuleNotFound {
83 path: imported_path_str.to_string(),
84 src: Arc::from(NamedSource::new(
85 file_path.to_string_lossy(),
86 source_arc.to_string(),
87 )),
88 span: (
89 import_statement.pos_start,
90 import_statement.pos_end - import_statement.pos_start,
91 )
92 .into(),
93 }
94 })?;
95
96 let mut parser = crate::parser::Parser::new_with_name(
98 &imported_source_text,
99 absolute_imported_path.to_string_lossy().to_string(),
100 )?;
101 let imported_document = parser.parse_document()?;
102
103 let resolved_imported_document = self.resolve(
105 imported_document,
106 &imported_source_text,
107 absolute_imported_path.clone(),
108 Some(import_statement.clone()), )?;
110
111 self.resolved_documents
112 .insert(absolute_imported_path, resolved_imported_document);
113 }
114
115 for import_statement in &document.imports {
117 if let ImportSpec::Named(specifiers) = &import_statement.spec {
118 let imported_path_str = import_statement.path.trim_matches('"');
119 let absolute_imported_path = current_dir.join(imported_path_str);
120 if let Some(imported_doc) = self.resolved_documents.get(&absolute_imported_path) {
121 if let MonValueKind::Object(members) = &imported_doc.root.kind {
122 for specifier in specifiers {
123 if !specifier.is_anchor {
124 for member in members {
125 if let Member::TypeDefinition(td) = member {
126 if td.name == specifier.name {
127 self.symbol_table
128 .types
129 .insert(specifier.name.clone(), td.clone());
130 }
131 }
132 }
133 }
134 }
135 }
136 }
137 }
138 }
139
140 if let MonValueKind::Object(members) = &document.root.kind {
142 for member in members {
143 match member {
144 Member::TypeDefinition(type_def) => {
145 self.symbol_table
146 .types
147 .insert(type_def.name.clone(), type_def.clone());
148 }
149 Member::Pair(pair) => {
150 if let Some(anchor_name) = &pair.value.anchor {
151 self.anchors.insert(anchor_name.clone(), pair.value.clone());
152 }
153 }
154 _ => {}
155 }
156 }
157 }
158
159 let resolved_root = self.resolve_value(document.root, &file_path, source_text)?;
161
162 let final_resolved_root =
166 self.validate_document_root(resolved_root, &document.imports, &file_path, source_text)?;
167
168 let resolved_doc = MonDocument {
169 root: final_resolved_root,
170 imports: document.imports, };
172
173 self.resolving_stack.pop();
175
176 Ok(resolved_doc)
177 }
178 fn resolve_value(
180 &mut self,
181 mut value: MonValue,
182 file_path: &PathBuf,
183 source_text: &str,
184 ) -> Result<MonValue, ResolverError> {
185 let alias_span = value.get_source_span();
186
187 match &mut value.kind {
188 MonValueKind::Alias(alias_name) => {
189 let anchor_value = self.anchors.get(alias_name).ok_or_else(|| {
191 ResolverError::AnchorNotFound {
193 name: alias_name.clone(),
194 src: Arc::from(NamedSource::new(
195 file_path.to_string_lossy(),
196 source_text.to_string(),
197 )),
198 span: alias_span,
199 }
200 })?;
201 Ok(anchor_value.clone()) }
203 MonValueKind::Object(members) => {
204 let mut resolved_members = Vec::new();
205 for member in members.drain(..) {
206 match member {
207 Member::Spread(spread_name) => {
208 let anchor_value = self.anchors.get(&spread_name).ok_or_else(|| {
210 ResolverError::AnchorNotFound {
212 name: spread_name.clone(),
213 src: Arc::from(NamedSource::new(
214 file_path.to_string_lossy(),
215 source_text.to_string(),
216 )),
217 span: alias_span,
218 }
219 })?;
220 if let MonValueKind::Object(spread_members) = &anchor_value.kind {
221 let spread_members_clone = spread_members.clone();
222 for spread_member in spread_members_clone {
223 resolved_members.push(self.resolve_value_member(
225 spread_member,
226 file_path,
227 source_text,
228 )?);
229 }
230 } else {
231 return Err(ResolverError::SpreadOnNonObject {
232 name: spread_name.clone(),
233 src: Arc::from(NamedSource::new(
234 file_path.to_string_lossy(),
235 source_text.to_string(),
236 )),
237 span: alias_span,
238 });
239 }
240 }
241 _ => {
242 resolved_members.push(self.resolve_value_member(
244 member,
245 file_path,
246 source_text,
247 )?);
248 }
249 }
250 }
251 let mut final_members_map: HashMap<String, Member> = HashMap::new();
253 for member in resolved_members {
254 if let Member::Pair(pair) = member {
255 final_members_map.insert(pair.key.clone(), Member::Pair(pair));
256 } else {
257 final_members_map.insert(format!("{member:?}"), member);
260 }
262 }
263 let final_members = final_members_map.into_values().collect();
264 Ok(MonValue {
265 kind: MonValueKind::Object(final_members),
266 anchor: value.anchor,
267 pos_start: value.pos_start,
268 pos_end: value.pos_end,
269 })
270 }
271 MonValueKind::Array(elements) => {
272 let mut resolved_elements = Vec::new();
273 for element in elements.drain(..) {
274 match element.kind {
275 MonValueKind::ArraySpread(spread_name) => {
276 let anchor_value = self.anchors.get(&spread_name).ok_or_else(|| {
278 ResolverError::AnchorNotFound {
280 name: spread_name.clone(),
281 src: Arc::from(NamedSource::new(
282 file_path.to_string_lossy(),
283 source_text.to_string(),
284 )),
285 span: alias_span,
286 }
287 })?;
288 if let MonValueKind::Array(spread_elements) = &anchor_value.kind {
289 let spread_elements_clone = spread_elements.clone();
290 for spread_element in spread_elements_clone {
291 resolved_elements.push(self.resolve_value(
293 spread_element,
294 file_path,
295 source_text,
296 )?);
297 }
298 } else {
299 return Err(ResolverError::SpreadOnNonArray {
301 name: spread_name.clone(),
302 src: Arc::from(NamedSource::new(
303 file_path.to_string_lossy(),
304 source_text.to_string(),
305 )),
306 span: alias_span,
307 });
308 }
309 }
310 _ => {
311 resolved_elements.push(self.resolve_value(
313 element,
314 file_path,
315 source_text,
316 )?);
317 }
318 }
319 }
320 Ok(MonValue {
321 kind: MonValueKind::Array(resolved_elements),
322 anchor: value.anchor,
323 pos_start: value.pos_start,
324 pos_end: value.pos_end,
325 })
326 }
327 _ => Ok(value), }
329 }
330
331 fn resolve_value_member(
333 &mut self,
334 mut member: Member,
335 file_path: &PathBuf,
336 source_text: &str,
337 ) -> Result<Member, ResolverError> {
338 match &mut member {
339 Member::Pair(pair) => {
340 pair.value = self.resolve_value(pair.value.clone(), file_path, source_text)?;
341 Ok(member)
342 }
343 _ => Ok(member),
345 }
346 }
347 fn validate_document_root(
349 &mut self,
350 mut root_value: MonValue,
351 imports: &[ImportStatement], file_path: &PathBuf,
353 source_text: &str,
354 ) -> Result<MonValue, ResolverError> {
355 if let MonValueKind::Object(members) = &mut root_value.kind {
356 for member in members.iter_mut() {
357 if let Member::Pair(pair) = member {
358 if let Some(type_spec) = &pair.validation {
359 self.validate_value(
361 &mut pair.value,
362 type_spec,
363 &pair.key,
364 imports, file_path,
366 source_text,
367 )?;
368 }
369 }
370 }
371 }
372 Ok(root_value)
373 }
374
375 fn validate_value(
377 &mut self,
378 value: &mut MonValue,
379 type_spec: &TypeSpec,
380 field_name: &str, imports: &[ImportStatement], file_path: &PathBuf,
383 source_text: &str,
384 ) -> Result<(), ResolverError> {
385 match type_spec {
386 TypeSpec::Simple(type_name, _) => {
387 match type_name.as_str() {
389 "String" => {
390 if !matches!(value.kind, MonValueKind::String(_)) {
391 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
392 field_name: field_name.to_string(),
393 expected_type: "String".to_string(),
394 found_type: format!("{:?}", value.kind),
395 src: Arc::from(NamedSource::new(
396 file_path.to_string_lossy(),
397 source_text.to_string(),
398 )),
399 span: (value.pos_start, value.pos_end - value.pos_start).into(),
400 }));
401 }
402 }
403 "Number" => {
404 if !matches!(value.kind, MonValueKind::Number(_)) {
405 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
406 field_name: field_name.to_string(),
407 expected_type: "Number".to_string(),
408 found_type: format!("{:?}", value.kind),
409 src: Arc::from(NamedSource::new(
410 file_path.to_string_lossy(),
411 source_text.to_string(),
412 )),
413 span: (value.pos_start, value.pos_end - value.pos_start).into(),
414 }));
415 }
416 }
417 "Boolean" => {
418 if !matches!(value.kind, MonValueKind::Boolean(_)) {
419 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
420 field_name: field_name.to_string(),
421 expected_type: "Boolean".to_string(),
422 found_type: format!("{:?}", value.kind),
423 src: Arc::from(NamedSource::new(
424 file_path.to_string_lossy(),
425 source_text.to_string(),
426 )),
427 span: (value.pos_start, value.pos_end - value.pos_start).into(),
428 }));
429 }
430 }
431 "Null" => {
432 if !matches!(value.kind, MonValueKind::Null) {
433 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
434 field_name: field_name.to_string(),
435 expected_type: "Null".to_string(),
436 found_type: format!("{:?}", value.kind),
437 src: Arc::from(NamedSource::new(
438 file_path.to_string_lossy(),
439 source_text.to_string(),
440 )),
441 span: (value.pos_start, value.pos_end - value.pos_start).into(),
442 }));
443 }
444 }
445 "Object" => {
446 if !matches!(value.kind, MonValueKind::Object(_)) {
447 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
448 field_name: field_name.to_string(),
449 expected_type: "Object".to_string(),
450 found_type: format!("{:?}", value.kind),
451 src: Arc::from(NamedSource::new(
452 file_path.to_string_lossy(),
453 source_text.to_string(),
454 )),
455 span: (value.pos_start, value.pos_end - value.pos_start).into(),
456 }));
457 }
458 }
459 "Array" => {
460 if !matches!(value.kind, MonValueKind::Array(_)) {
461 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
462 field_name: field_name.to_string(),
463 expected_type: "Array".to_string(),
464 found_type: format!("{:?}", value.kind),
465 src: Arc::from(NamedSource::new(
466 file_path.to_string_lossy(),
467 source_text.to_string(),
468 )),
469 span: (value.pos_start, value.pos_end - value.pos_start).into(),
470 }));
471 }
472 }
473 "Any" => { }
474 _ => {
475 let (namespace, type_name_part) =
477 if let Some((ns, tn)) = type_name.split_once('.') {
478 (Some(ns), tn)
479 } else {
480 (None, type_name.as_str())
481 };
482
483 let type_def = if let Some(namespace) = namespace {
484 let import_statement = imports
486 .iter()
487 .find(|i| {
488 if let ImportSpec::Namespace(ns) = &i.spec {
489 ns == namespace
490 } else {
491 false
492 }
493 })
494 .ok_or_else(|| {
495 ResolverError::Validation(ValidationError::UndefinedType {
496 type_name: type_name.clone(),
497 src: Arc::from(NamedSource::new(
498 file_path.to_string_lossy(),
499 source_text.to_string(),
500 )),
501 span: (value.pos_start, value.pos_end - value.pos_start)
502 .into(),
503 })
504 })?;
505
506 let imported_path_str = import_statement.path.trim_matches('"');
507 let parent_dir = file_path.parent().ok_or_else(|| {
508 ResolverError::ModuleNotFound {
511 path: import_statement.path.clone(),
512 src: Arc::from(NamedSource::new(
513 file_path.to_string_lossy(),
514 source_text.to_string(),
515 )),
516 span: (
517 import_statement.pos_start,
518 import_statement.pos_end - import_statement.pos_start,
519 )
520 .into(),
521 }
522 })?;
523 let absolute_imported_path = parent_dir.join(imported_path_str);
524
525 let imported_doc = self
526 .resolved_documents
527 .get(&absolute_imported_path)
528 .ok_or_else(|| {
529 ResolverError::ModuleNotFound {
532 path: absolute_imported_path.to_string_lossy().to_string(),
533 src: Arc::from(NamedSource::new(
534 file_path.to_string_lossy(),
535 source_text.to_string(),
536 )),
537 span: (value.pos_start, value.pos_end - value.pos_start)
538 .into(),
539 }
540 })?;
541
542 if let MonValueKind::Object(members) = &imported_doc.root.kind {
543 members.iter().find_map(|m| {
544 if let Member::TypeDefinition(td) = m {
545 if td.name == type_name_part {
546 return Some(td.def_type.clone());
547 }
548 }
549 None
550 })
551 } else {
552 None
553 }
554 } else {
555 self.symbol_table
556 .types
557 .get(type_name_part)
558 .map(|td| td.def_type.clone())
559 };
560
561 if let Some(type_def) = type_def {
562 match type_def {
563 TypeDef::Struct(struct_def) => {
564 if let MonValueKind::Object(value_members) = &mut value.kind {
566 let mut value_map: HashMap<String, &mut MonValue> =
567 HashMap::new();
568 for member in value_members.iter_mut() {
569 if let Member::Pair(pair) = member {
570 value_map.insert(pair.key.clone(), &mut pair.value);
571 }
572 }
573
574 let mut new_members = Vec::new();
575 for field_def in &struct_def.fields {
576 if let Some(field_value) =
577 value_map.get_mut(&field_def.name)
578 {
579 self.validate_value(
581 field_value,
582 &field_def.type_spec,
583 &field_def.name,
584 imports, file_path,
586 source_text,
587 )?;
588 } else {
589 if field_def.default_value.is_none() {
591 return Err(ResolverError::Validation(
592 ValidationError::MissingField {
593 field_name: field_def.name.clone(),
594 struct_name: type_name.clone(),
595 src: Arc::from(NamedSource::new(
596 file_path.to_string_lossy(),
597 source_text.to_string(),
598 )),
599 span: (
600 value.pos_start,
601 value.pos_end - value.pos_start,
602 )
603 .into(),
604 },
605 ));
606 }
607 if let Some(default_value) =
610 &field_def.default_value
611 {
612 new_members.push(Member::Pair(
613 crate::ast::Pair {
614 key: field_def.name.clone(),
615 value: default_value.clone(),
616 validation: None,
617 },
618 ));
619 }
620 }
621 }
622 value_members.extend(new_members);
623
624 for member in value_members.iter() {
626 if let Member::Pair(pair) = member {
627 if !struct_def
628 .fields
629 .iter()
630 .any(|f| f.name == pair.key)
631 {
632 return Err(ResolverError::Validation(
633 ValidationError::UnexpectedField {
634 field_name: pair.key.clone(),
635 struct_name: type_name.clone(),
636 src: Arc::from(NamedSource::new(
637 file_path.to_string_lossy(),
638 source_text.to_string(),
639 )),
640 span: (
641 value.pos_start,
642 value.pos_end - value.pos_start,
643 )
644 .into(),
645 },
646 ));
647 }
648 }
649 }
650 } else {
651 return Err(ResolverError::Validation(
652 ValidationError::TypeMismatch {
653 field_name: field_name.to_string(),
654 expected_type: type_name.clone(),
655 found_type: format!("{:?}", value.kind),
656 src: Arc::from(NamedSource::new(
657 file_path.to_string_lossy(),
658 source_text.to_string(),
659 )),
660 span: (
661 value.pos_start,
662 value.pos_end - value.pos_start,
663 )
664 .into(),
665 },
666 ));
667 }
668 }
669 TypeDef::Enum(enum_def) => {
670 if let MonValueKind::EnumValue {
672 enum_name,
673 variant_name,
674 } = &value.kind
675 {
676 if enum_name != type_name {
677 return Err(ResolverError::Validation(
678 ValidationError::TypeMismatch {
679 field_name: field_name.to_string(),
680 expected_type: format!("enum '{type_name}'"),
681 found_type: format!("enum '{enum_name}'"),
682 src: Arc::from(NamedSource::new(
683 file_path.to_string_lossy(),
684 source_text.to_string(),
685 )),
686 span: (
687 value.pos_start,
688 value.pos_end - value.pos_start,
689 )
690 .into(),
691 },
692 ));
693 }
694 if !enum_def.variants.contains(variant_name) {
695 return Err(ResolverError::Validation(
696 ValidationError::UndefinedEnumVariant {
697 variant_name: variant_name.clone(),
698 enum_name: type_name.clone(),
699 src: Arc::from(NamedSource::new(
700 file_path.to_string_lossy(),
701 source_text.to_string(),
702 )),
703 span: (
704 value.pos_start,
705 value.pos_end - value.pos_start,
706 )
707 .into(),
708 },
709 ));
710 }
711 } else {
712 return Err(ResolverError::Validation(
713 ValidationError::TypeMismatch {
714 field_name: field_name.to_string(),
715 expected_type: format!("enum '{type_name}'"),
716 found_type: format!("{:?}", value.kind),
717 src: Arc::from(NamedSource::new(
718 file_path.to_string_lossy(),
719 source_text.to_string(),
720 )),
721 span: (
722 value.pos_start,
723 value.pos_end - value.pos_start,
724 )
725 .into(),
726 },
727 ));
728 }
729 }
730 }
731 } else {
732 return Err(ResolverError::Validation(
733 ValidationError::UndefinedType {
734 type_name: type_name.clone(),
735 src: Arc::from(NamedSource::new(
736 file_path.to_string_lossy(),
737 source_text.to_string(),
738 )),
739 span: (value.pos_start, value.pos_end - value.pos_start).into(),
740 },
741 ));
742 }
743 }
744 }
745 }
746 TypeSpec::Collection(collection_types, _) => {
747 if let MonValueKind::Array(elements) = &mut value.kind {
749 self.validate_collection(
750 elements,
751 collection_types,
752 field_name,
753 imports, file_path,
755 source_text,
756 )?;
757 } else {
758 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
759 field_name: field_name.to_string(),
760 expected_type: "Array".to_string(),
761 found_type: format!("{:?}", value.kind),
762 src: Arc::from(NamedSource::new(
763 file_path.to_string_lossy(),
764 source_text.to_string(),
765 )),
766 span: (value.pos_start, value.pos_end - value.pos_start).into(),
767 }));
768 }
769 }
770 TypeSpec::Spread(_, _) => {
771 return Ok(());
773 }
774 }
775 Ok(())
776 }
777
778 fn validate_collection(
779 &mut self,
780 elements: &mut [MonValue],
781 collection_types: &[TypeSpec],
782 field_name: &str,
783 imports: &[ImportStatement], file_path: &PathBuf,
785 source_text: &str,
786 ) -> Result<(), ResolverError> {
787 if collection_types.len() == 1 && !matches!(collection_types[0], TypeSpec::Spread(_, _)) {
789 self.validate_value(
790 &mut elements[0],
791 &collection_types[0],
792 field_name,
793 imports, file_path,
795 source_text,
796 )?;
797 return Ok(());
798 }
799
800 if collection_types.len() == 1 && matches!(collection_types[0], TypeSpec::Spread(_, _)) {
802 if let TypeSpec::Spread(inner_type, _) = &collection_types[0] {
803 for element in elements {
804 self.validate_value(
805 element,
806 inner_type,
807 field_name,
808 imports,
809 file_path,
810 source_text,
811 )?;
812 }
813 return Ok(());
814 }
815 }
816
817 let has_spread = collection_types
819 .iter()
820 .any(|t| matches!(t, TypeSpec::Spread(_, _)));
821 if !has_spread {
822 if elements.len() != collection_types.len() {
823 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
825 field_name: field_name.to_string(),
826 expected_type: format!("tuple with {} elements", collection_types.len()),
827 found_type: format!("tuple with {} elements", elements.len()),
828 src: Arc::from(NamedSource::new(
829 file_path.to_string_lossy(),
830 source_text.to_string(),
831 )),
832 span: (
833 elements.first().map_or(0, |e| e.pos_start),
834 elements.last().map_or(0, |e| e.pos_end)
835 - elements.first().map_or(0, |e| e.pos_start),
836 )
837 .into(),
838 }));
839 }
840 for (i, element) in elements.iter_mut().enumerate() {
841 self.validate_value(
842 element,
843 &collection_types[i],
844 field_name,
845 imports, file_path,
847 source_text,
848 )?;
849 }
850 return Ok(());
851 }
852
853 if collection_types.len() == 2
855 && !matches!(collection_types[0], TypeSpec::Spread(_, _))
856 && matches!(collection_types[1], TypeSpec::Spread(_, _))
857 {
858 if elements.is_empty() {
859 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
860 field_name: field_name.to_string(),
861 expected_type: "array with at least 1 element".to_string(),
862 found_type: "empty array".to_string(),
863 src: Arc::from(NamedSource::new(
864 file_path.to_string_lossy(),
865 source_text.to_string(),
866 )),
867 span: (
868 elements.first().map_or(0, |e| e.pos_start),
869 elements.last().map_or(0, |e| e.pos_end)
870 - elements.first().map_or(0, |e| e.pos_start),
871 )
872 .into(),
873 }));
874 }
875 self.validate_value(
876 &mut elements[0],
877 &collection_types[0],
878 field_name,
879 imports, file_path,
881 source_text,
882 )?;
883 if let TypeSpec::Spread(inner_type, _) = &collection_types[1] {
884 for element in &mut elements[1..] {
885 self.validate_value(
886 element,
887 inner_type,
888 field_name,
889 imports,
890 file_path,
891 source_text,
892 )?;
893 }
894 }
895 return Ok(());
896 }
897
898 if collection_types.len() == 2
900 && matches!(collection_types[0], TypeSpec::Spread(_, _))
901 && !matches!(collection_types[1], TypeSpec::Spread(_, _))
902 {
903 if elements.is_empty() {
904 return Err(ResolverError::Validation(ValidationError::TypeMismatch {
905 field_name: field_name.to_string(),
906 expected_type: "array with at least 1 element".to_string(),
907 found_type: "empty array".to_string(),
908 src: Arc::from(NamedSource::new(
909 file_path.to_string_lossy(),
910 source_text.to_string(),
911 )),
912 span: (
913 elements.first().map_or(0, |e| e.pos_start),
914 elements.last().map_or(0, |e| e.pos_end)
915 - elements.first().map_or(0, |e| e.pos_start),
916 )
917 .into(),
918 }));
919 }
920 let (head, last) = elements.split_at_mut(elements.len() - 1);
921 self.validate_value(
922 last.first_mut().unwrap(), &collection_types[1],
924 field_name,
925 imports, file_path,
927 source_text,
928 )?;
929 if let TypeSpec::Spread(inner_type, _) = &collection_types[0] {
930 for element in head {
931 self.validate_value(
932 element,
933 inner_type,
934 field_name,
935 imports,
936 file_path,
937 source_text,
938 )?;
939 }
940 }
941 return Ok(());
942 }
943
944 Err(ResolverError::Validation(
946 ValidationError::UnimplementedCollectionValidation {
947 field_name: field_name.to_string(),
948 src: Arc::from(NamedSource::new(
949 file_path.to_string_lossy(),
950 source_text.to_string(),
951 )),
952 span: (
953 elements.first().map_or(0, |e| e.pos_start),
954 elements.last().map_or(0, |e| e.pos_end)
955 - elements.first().map_or(0, |e| e.pos_start),
956 )
957 .into(),
958 },
959 ))
960 }
961}
962
963impl Default for Resolver {
964 fn default() -> Self {
965 Self::new()
966 }
967}
968
969#[cfg(test)]
970mod tests {
971 use crate::parser::Parser;
972 use crate::resolver::Resolver;
973 use miette::Report;
974 use std::fs;
975 use std::path::PathBuf;
976
977 fn resolve_ok(source: &str, file_name: &str) -> crate::ast::MonDocument {
978 let mut parser = Parser::new_with_name(source, file_name.to_string()).unwrap();
979 let document = parser.parse_document().unwrap();
980 let mut resolver = Resolver::new();
981 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
982 path.push(file_name);
983 match resolver.resolve(document, source, path, None) {
984 Ok(doc) => doc,
985 Err(err) => {
986 let report = Report::from(err);
987 panic!("{report:#}");
988 }
989 }
990 }
991
992 fn resolve_err(source: &str, file_name: &str) -> crate::error::ResolverError {
993 let mut parser = Parser::new_with_name(source, file_name.to_string()).unwrap();
994 let document = parser.parse_document().unwrap();
995 let mut resolver = Resolver::new();
996 let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
997 path.push(file_name);
998 match resolver.resolve(document, source, path, None) {
999 Ok(_) => panic!("Expected a ResolverError, but got Ok"),
1000 Err(err) => err,
1001 }
1002 }
1003
1004 #[test]
1005 fn test_simple_alias_resolution() {
1006 let source = r"{ &my_value: 123, alias_value: *my_value }";
1007 let doc = resolve_ok(source, "test.mon");
1008
1009 let root_object = match doc.root.kind {
1010 crate::ast::MonValueKind::Object(members) => members,
1011 _ => panic!("Expected an object"),
1012 };
1013
1014 let alias_member = root_object
1016 .iter()
1017 .find(|m| {
1018 if let crate::ast::Member::Pair(p) = m {
1019 p.key == "alias_value"
1020 } else {
1021 false
1022 }
1023 })
1024 .unwrap();
1025
1026 if let crate::ast::Member::Pair(p) = alias_member {
1027 assert_eq!(p.value.kind, crate::ast::MonValueKind::Number(123.0));
1028 } else {
1029 panic!("Expected a pair member");
1030 }
1031 }
1032
1033 #[test]
1034 fn test_object_spread_resolution() {
1035 let source = r#"{
1036 &base_config: { host: "localhost", port: 8080 },
1037 app_config: {
1038 ...*base_config,
1039 port: 9000, // Override
1040 debug: true,
1041 }
1042 }"#;
1043 let doc = resolve_ok(source, "test.mon");
1044
1045 let root_object = match doc.root.kind {
1046 crate::ast::MonValueKind::Object(members) => members,
1047 _ => panic!("Expected an object"),
1048 };
1049
1050 let app_config_member = root_object
1051 .iter()
1052 .find(|m| {
1053 if let crate::ast::Member::Pair(p) = m {
1054 p.key == "app_config"
1055 } else {
1056 false
1057 }
1058 })
1059 .unwrap();
1060
1061 if let crate::ast::Member::Pair(p) = app_config_member {
1062 let app_config_object = match &p.value.kind {
1063 crate::ast::MonValueKind::Object(members) => members,
1064 _ => panic!("Expected app_config to be an object"),
1065 };
1066
1067 let host_member = app_config_object
1069 .iter()
1070 .find(|m| {
1071 if let crate::ast::Member::Pair(p) = m {
1072 p.key == "host"
1073 } else {
1074 false
1075 }
1076 })
1077 .unwrap();
1078 if let crate::ast::Member::Pair(p) = host_member {
1079 assert_eq!(
1080 p.value.kind,
1081 crate::ast::MonValueKind::String("localhost".to_string())
1082 );
1083 } else {
1084 panic!("Expected host to be a pair");
1085 }
1086
1087 let port_member = app_config_object
1089 .iter()
1090 .find(|m| {
1091 if let crate::ast::Member::Pair(p) = m {
1092 p.key == "port"
1093 } else {
1094 false
1095 }
1096 })
1097 .unwrap();
1098 if let crate::ast::Member::Pair(p) = port_member {
1099 assert_eq!(p.value.kind, crate::ast::MonValueKind::Number(9000.0));
1100 } else {
1101 panic!("Expected port to be a pair");
1102 }
1103
1104 let debug_member = app_config_object
1106 .iter()
1107 .find(|m| {
1108 if let crate::ast::Member::Pair(p) = m {
1109 p.key == "debug"
1110 } else {
1111 false
1112 }
1113 })
1114 .unwrap();
1115 if let crate::ast::Member::Pair(p) = debug_member {
1116 assert_eq!(p.value.kind, crate::ast::MonValueKind::Boolean(true));
1117 } else {
1118 panic!("Expected debug to be a pair");
1119 }
1120 } else {
1121 panic!("Expected app_config to be a pair member");
1122 }
1123 }
1124
1125 #[test]
1126 fn test_array_spread_resolution() {
1127 let source = r#"{
1128 &base_tags: ["tag1", "tag2"],
1129 item_tags: [
1130 "start",
1131 ...*base_tags,
1132 "end",
1133 ]
1134 }"#;
1135 let doc = resolve_ok(source, "test.mon");
1136
1137 let root_object = match doc.root.kind {
1138 crate::ast::MonValueKind::Object(members) => members,
1139 _ => panic!("Expected an object"),
1140 };
1141
1142 let item_tags_member = root_object
1143 .iter()
1144 .find(|m| {
1145 if let crate::ast::Member::Pair(p) = m {
1146 p.key == "item_tags"
1147 } else {
1148 false
1149 }
1150 })
1151 .unwrap();
1152
1153 if let crate::ast::Member::Pair(p) = item_tags_member {
1154 let item_tags_array = match &p.value.kind {
1155 crate::ast::MonValueKind::Array(elements) => elements,
1156 _ => panic!("Expected item_tags to be an array"),
1157 };
1158
1159 assert_eq!(item_tags_array.len(), 4);
1160 assert_eq!(
1161 item_tags_array[0].kind,
1162 crate::ast::MonValueKind::String("start".to_string())
1163 );
1164 assert_eq!(
1165 item_tags_array[1].kind,
1166 crate::ast::MonValueKind::String("tag1".to_string())
1167 );
1168 assert_eq!(
1169 item_tags_array[2].kind,
1170 crate::ast::MonValueKind::String("tag2".to_string())
1171 );
1172 assert_eq!(
1173 item_tags_array[3].kind,
1174 crate::ast::MonValueKind::String("end".to_string())
1175 );
1176 } else {
1177 panic!("Expected item_tags to be a pair member");
1178 }
1179 }
1180
1181 #[test]
1182 fn test_struct_validation_with_defaults_and_collections_ok() {
1183 let source = r#"
1184 {
1185 User: #struct {
1186 id(Number),
1187 name(String),
1188 email(String) = "default@example.com",
1189 is_active(Boolean) = true,
1190 roles([String...]),
1191 permissions([String, Number]),
1192 log_data([String, Any...]),
1193 status_history([Boolean..., String]),
1194 },
1195
1196 // Valid user with defaults
1197 user1 :: User = {
1198 id: 1,
1199 name: "Alice",
1200 roles: ["admin", "editor"],
1201 permissions: ["read", 1],
1202 log_data: ["login", { timestamp: "...", ip: "..." }],
1203 status_history: [true, false, "active"],
1204 },
1205
1206 // Valid user, omitting optional fields
1207 user2 :: User = {
1208 id: 2,
1209 name: "Bob",
1210 roles: [],
1211 permissions: ["write", 2],
1212 log_data: ["logout"],
1213 status_history: ["inactive"],
1214 },
1215 }
1216 "#;
1217
1218 let doc = resolve_ok(source, "test_validation.mon");
1220 let root_object = match doc.root.kind {
1221 crate::ast::MonValueKind::Object(members) => members,
1222 _ => panic!("Expected an object"),
1223 };
1224
1225 let user1_member = root_object
1226 .iter()
1227 .find(|m| {
1228 if let crate::ast::Member::Pair(p) = m {
1229 p.key == "user1"
1230 } else {
1231 false
1232 }
1233 })
1234 .unwrap();
1235
1236 if let crate::ast::Member::Pair(p) = user1_member {
1237 let user1_object = match &p.value.kind {
1238 crate::ast::MonValueKind::Object(members) => members,
1239 _ => panic!("Expected user1 to be an object"),
1240 };
1241
1242 let email_member = user1_object
1244 .iter()
1245 .find(|m| {
1246 if let crate::ast::Member::Pair(p) = m {
1247 p.key == "email"
1248 } else {
1249 false
1250 }
1251 })
1252 .unwrap();
1253 if let crate::ast::Member::Pair(p) = email_member {
1254 assert_eq!(
1255 p.value.kind,
1256 crate::ast::MonValueKind::String("default@example.com".to_string())
1257 );
1258 } else {
1259 panic!("Expected email to be a pair");
1260 }
1261
1262 let is_active_member = user1_object
1264 .iter()
1265 .find(|m| {
1266 if let crate::ast::Member::Pair(p) = m {
1267 p.key == "is_active"
1268 } else {
1269 false
1270 }
1271 })
1272 .unwrap();
1273 if let crate::ast::Member::Pair(p) = is_active_member {
1274 assert_eq!(p.value.kind, crate::ast::MonValueKind::Boolean(true));
1275 } else {
1276 panic!("Expected is_active to be a pair");
1277 }
1278 } else {
1279 panic!("Expected user1 to be a pair member");
1280 }
1281 }
1282
1283 #[test]
1284 fn test_struct_validation_missing_required_field() {
1285 let source = r"
1286 {
1287 User: #struct { id(Number), name(String) },
1288 invalid_user :: User = { id: 3 },
1289 }
1290 ";
1291 let err = resolve_err(source, "test_validation.mon");
1292 match err {
1293 crate::error::ResolverError::Validation(
1294 crate::error::ValidationError::MissingField { field_name, .. },
1295 ) => {
1296 assert_eq!(field_name, "name");
1297 }
1298 _ => panic!("Expected MissingField error, but got {err:?}"),
1299 }
1300 }
1301
1302 #[test]
1303 fn test_struct_validation_wrong_id_type() {
1304 let source = r#"
1305 {
1306 User: #struct { id(Number), name(String) },
1307 invalid_user :: User = { id: "four", name: "Charlie" },
1308 }
1309 "#;
1310 let err = resolve_err(source, "test_validation.mon");
1311 match err {
1312 crate::error::ResolverError::Validation(
1313 crate::error::ValidationError::TypeMismatch {
1314 field_name,
1315 expected_type,
1316 found_type,
1317 ..
1318 },
1319 ) => {
1320 assert_eq!(field_name, "id");
1321 assert_eq!(expected_type, "Number");
1322 assert!(found_type.contains("String"));
1323 }
1324 _ => panic!("Expected TypeMismatch error for id, but got {err:?}"),
1325 }
1326 }
1327
1328 #[test]
1329 fn test_struct_validation_unexpected_field() {
1330 let source = r#"
1331 {
1332 User: #struct { id(Number), name(String) },
1333 invalid_user :: User = { id: 5, name: "David", age: 30 },
1334 }
1335 "#;
1336 let err = resolve_err(source, "test_validation.mon");
1337 match err {
1338 crate::error::ResolverError::Validation(
1339 crate::error::ValidationError::UnexpectedField { field_name, .. },
1340 ) => {
1341 assert_eq!(field_name, "age");
1342 }
1343 _ => panic!("Expected UnexpectedField error, but got {err:?}"),
1344 }
1345 }
1346
1347 #[test]
1348 fn test_struct_validation_roles_type_mismatch() {
1349 let source = r#"
1350 {
1351 User: #struct { roles([String...]) },
1352 invalid_user :: User = { roles: ["viewer", 123] },
1353 }
1354 "#;
1355 let err = resolve_err(source, "test_validation.mon");
1356 match err {
1357 crate::error::ResolverError::Validation(
1358 crate::error::ValidationError::TypeMismatch {
1359 field_name,
1360 expected_type,
1361 found_type,
1362 ..
1363 },
1364 ) => {
1365 assert_eq!(field_name, "roles");
1366 assert_eq!(expected_type, "String");
1367 assert!(found_type.contains("Number"));
1368 }
1369 _ => panic!("Expected TypeMismatch error for roles, but got {err:?}"),
1370 }
1371 }
1372
1373 #[test]
1374 fn test_struct_validation_permissions_length_mismatch() {
1375 let source = r#"
1376 {
1377 User: #struct { permissions([String, Number]) },
1378 invalid_user :: User = { permissions: ["read"] },
1379 }
1380 "#;
1381 let err = resolve_err(source, "test_validation.mon");
1382 match err {
1383 crate::error::ResolverError::Validation(
1384 crate::error::ValidationError::TypeMismatch {
1385 field_name,
1386 expected_type,
1387 found_type,
1388 ..
1389 },
1390 ) => {
1391 assert_eq!(field_name, "permissions");
1392 assert!(expected_type.contains("tuple with 2 elements"));
1393 assert!(found_type.contains("tuple with 1 elements"));
1394 }
1395 _ => panic!("Expected TypeMismatch error for permissions length, but got {err:?}"),
1396 }
1397 }
1398
1399 #[test]
1400 fn test_struct_validation_permissions_type_mismatch() {
1401 let source = r#"
1402 {
1403 User: #struct { permissions([String, Number]) },
1404 invalid_user :: User = { permissions: [8, "write"] },
1405 }
1406 "#;
1407 let err = resolve_err(source, "test_validation.mon");
1408 match err {
1409 crate::error::ResolverError::Validation(
1410 crate::error::ValidationError::TypeMismatch {
1411 field_name,
1412 expected_type,
1413 found_type,
1414 ..
1415 },
1416 ) => {
1417 assert_eq!(field_name, "permissions");
1418 assert_eq!(expected_type, "String");
1419 assert!(found_type.contains("Number"));
1420 }
1421 _ => panic!("Expected TypeMismatch error for permissions types, but got {err:?}"),
1422 }
1423 }
1424
1425 #[test]
1426 fn test_struct_validation_log_data_first_type_mismatch() {
1427 let source = r#"
1428 {
1429 User: #struct { log_data([String, Any...]) },
1430 invalid_user :: User = { log_data: [123, "event"] },
1431 }
1432 "#;
1433 let err = resolve_err(source, "test_validation.mon");
1434 match err {
1435 crate::error::ResolverError::Validation(
1436 crate::error::ValidationError::TypeMismatch {
1437 field_name,
1438 expected_type,
1439 found_type,
1440 ..
1441 },
1442 ) => {
1443 assert_eq!(field_name, "log_data");
1444 assert_eq!(expected_type, "String");
1445 assert!(found_type.contains("Number"));
1446 }
1447 _ => panic!("Expected TypeMismatch error for log_data first type, but got {err:?}"),
1448 }
1449 }
1450
1451 #[test]
1452 fn test_struct_validation_status_history_last_type_mismatch() {
1453 let source = r"
1454 {
1455 User: #struct { status_history([Boolean..., String]) },
1456 invalid_user :: User = { status_history: [true, 123] },
1457 }
1458 ";
1459 let err = resolve_err(source, "test_validation.mon");
1460 match err {
1461 crate::error::ResolverError::Validation(
1462 crate::error::ValidationError::TypeMismatch {
1463 field_name,
1464 expected_type,
1465 found_type,
1466 ..
1467 },
1468 ) => {
1469 assert_eq!(field_name, "status_history");
1470 assert_eq!(expected_type, "String");
1471 assert!(found_type.contains("Number"));
1472 }
1473 _ => {
1474 panic!("Expected TypeMismatch error for status_history last type, but got {err:?}")
1475 }
1476 }
1477 }
1478
1479 #[test]
1480 fn test_nested_struct_validation_ok() {
1481 let source = r#"
1482 {
1483 Profile: #struct {
1484 username(String),
1485 email(String),
1486 },
1487 User: #struct {
1488 id(Number),
1489 profile(Profile),
1490 },
1491
1492 // Valid nested struct
1493 user1 :: User = {
1494 id: 1,
1495 profile: {
1496 username: "alice",
1497 email: "alice@example.com",
1498 },
1499 },
1500 }
1501 "#;
1502
1503 resolve_ok(source, "test_nested_ok.mon");
1504 }
1505
1506 #[test]
1507 fn test_nested_struct_validation_err() {
1508 let source = r#"
1509 {
1510 Profile: #struct {
1511 username(String),
1512 email(String),
1513 },
1514 User: #struct {
1515 id(Number),
1516 profile(Profile),
1517 },
1518
1519 // Invalid: Nested struct has wrong type for username
1520 user2 :: User = {
1521 id: 2,
1522 profile: {
1523 username: 123,
1524 email: "bob@example.com",
1525 },
1526 },
1527 }
1528 "#;
1529
1530 let err = resolve_err(source, "test_nested_err.mon");
1531 match err {
1532 crate::error::ResolverError::Validation(
1533 crate::error::ValidationError::TypeMismatch {
1534 field_name,
1535 expected_type,
1536 found_type,
1537 ..
1538 },
1539 ) => {
1540 assert_eq!(field_name, "username");
1541 assert_eq!(expected_type, "String");
1542 assert!(found_type.contains("Number"));
1543 }
1544 _ => panic!("Expected TypeMismatch error for username, but got {err:?}"),
1545 }
1546 }
1547
1548 #[test]
1549 fn test_cross_file_validation() {
1550 let source = fs::read_to_string("tests/cross_file_main.mon").unwrap();
1551 resolve_ok(&source, "tests/cross_file_main.mon");
1552 }
1553
1554 #[test]
1555 fn test_parser_for_schemas_file() {
1556 let source = fs::read_to_string("tests/cross_file_schemas.mon").unwrap();
1557 let mut parser = Parser::new_with_name(&source, "test.mon".to_string()).unwrap();
1558 let _ = parser.parse_document().unwrap();
1559 }
1560
1561 #[test]
1562 fn test_named_import_validation() {
1563 let source = fs::read_to_string("tests/named_import_main.mon").unwrap();
1564 resolve_ok(&source, "tests/named_import_main.mon");
1565 }
1566}