mon_core/
resolver.rs

1//! # MON Semantic Analyzer (Resolver)
2//!
3//! This module is responsible for the semantic analysis phase of the MON compilation pipeline.
4//! The [`Resolver`] traverses the Abstract Syntax Tree (AST) generated by the [`Parser`](crate::parser::Parser)
5//! and enriches it with semantic meaning.
6//!
7//! ## Architectural Overview
8//!
9//! The resolver is a stateful visitor that walks the AST and performs several key actions:
10//!
11//! - **Import Resolution**: It finds, parses, and resolves `import` statements. It maintains a cache of
12//!   already resolved documents to avoid redundant work and uses a resolving stack to detect circular
13//!   dependencies between files. It also handles the `mon:` URI scheme for built-in schemas.
14//!
15//! - **Symbol & Anchor Collection**: It populates a global symbol table with type definitions (`#struct`, `#enum`)
16//!   and a global map with all declared anchors (`&my_anchor`).
17//!
18//! - **Alias and Spread Resolution**: It replaces all aliases (`*my_anchor`) and spreads (`...*my_anchor`)
19//!   with deep clones of their original values. This is where composition happens.
20//!
21//! - **Validation**: It performs type checking for any pair that has a validation specifier (`:: MyType`).
22//!   It ensures that structs have the correct fields, enums have valid variants, and that all types match
23//!   their definitions.
24//!
25//! After the resolver has finished, the AST is considered fully resolved and validated.
26//!
27//! ## Use Cases
28//!
29//! While you can use the resolver directly for fine-grained control, most users will interact
30//! with it via the top-level [`analyze`](crate::api::analyze) function. Direct use is beneficial when:
31//!
32//! - You need to inspect the symbol table or anchors after resolution.
33//! - You want to provide a custom path for built-in schemas.
34//! - You are building a tool that needs to hook into a specific part of the analysis lifecycle.
35//!
36//! ## Example: Direct Resolver Usage
37//!
38//! ```rust
39//! use mon_core::parser::Parser;
40//! use mon_core::resolver::Resolver;
41//! use mon_core::error::MonError;
42//! use std::path::PathBuf;
43//!
44//! # fn main() -> Result<(), MonError> {
45//! let source = r#"
46//! {
47//!     MyStruct: #struct { name(String) },
48//!     &my_data: { name: "Hello" },
49//!
50//!     // This instance will be validated against MyStruct.
51//!     instance :: MyStruct = *my_data
52//! }
53//! "#;
54//! let file_path = PathBuf::from("my_file.mon");
55//!
56//! // 1. The resolver needs a parsed document from the parser.
57//! let mut parser = Parser::new_with_name(source, file_path.to_string_lossy().to_string())?;
58//! let document = parser.parse_document()?;
59//!
60//! // 2. Create and run the resolver.
61//! let mut resolver = Resolver::new();
62//! let resolved_document = resolver.resolve(document.clone(), source, file_path, None)?;
63//!
64//! // 3. Inspect the results.
65//! assert!(resolver.symbol_table.types.contains_key("MyStruct"));
66//! assert!(resolver.anchors.contains_key("my_data"));
67//! # Ok(())
68//! # }
69//! ```
70use crate::ast::{
71    ImportSpec, ImportStatement, Member, MonDocument, MonValue, MonValueKind,
72    SymbolTable as AstSymbolTable, TypeDef, TypeSpec,
73};
74use crate::error::{ResolverError, ValidationError};
75use log::warn;
76use miette::NamedSource;
77use std::collections::HashMap;
78use std::path::{Path, PathBuf};
79use std::sync::Arc;
80
81/// Traverses a [`MonDocument`] to resolve imports, aliases, and spreads, and to validate data against schemas.
82///
83/// The `Resolver` is responsible for the semantic analysis of a parsed MON document from the [`Parser`].
84/// It is a crucial step that transforms a syntactically correct document into a fully validated and
85/// usable data structure.
86///
87/// It performs several key tasks:
88/// - **Import Resolution**: It parses and resolves `import` statements, loading symbols from other files
89///   and handling `mon:` URIs for built-in schemas. It also detects circular dependencies.
90/// - **Symbol Collection**: It gathers all type definitions (like structs and enums) and anchors
91///   into a global [`AstSymbolTable`].
92/// - **Alias & Spread Resolution**: It replaces aliases (e.g., `*my_anchor`) and spreads (e.g., `...*my_anchor`)
93///   with their corresponding values, effectively performing composition.
94/// - **Validation**: It validates data against declared types using the `::` operator (e.g.,
95///   `my_field :: MyType = ...`), ensuring type safety and data integrity. This includes checking
96///   for missing/extra fields in structs and validating collection types.
97///
98/// # Example: How to use the Resolver
99///
100/// While the main public entry point is the [`analyze`](crate::api::analyze) function, you can use
101/// the `Resolver` directly if you need more control over the process, such as inspecting the
102/// symbol table or using a custom built-in schema path.
103///
104/// ```rust
105/// use mon_core::parser::Parser;
106/// use mon_core::resolver::Resolver;
107/// use mon_core::error::MonError;
108/// use std::path::PathBuf;
109///
110/// # fn main() -> Result<(), MonError> {
111/// let source = r###"
112/// {
113///     MyStruct: #struct { name(String) },
114///     &my_data: { name: "Hello" },
115///
116///     // This instance will be validated against MyStruct.
117///     instance :: MyStruct = *my_data
118/// }
119/// "###;
120/// let file_path = PathBuf::from("my_file.mon");
121///
122/// // 1. The resolver needs a parsed document to work with.
123/// let mut parser = Parser::new_with_name(source, file_path.to_string_lossy().to_string())?;
124/// let document = parser.parse_document()?;
125///
126/// // 2. Create a new resolver.
127/// let mut resolver = Resolver::new();
128///
129/// // 3. Resolve the document. This returns the final, validated document.
130/// let resolved_document = resolver.resolve(document.clone(), source, file_path, None)?;
131///
132/// // You can now access the resolved data, symbol table, and anchors.
133/// assert!(resolver.symbol_table.types.contains_key("MyStruct"));
134/// assert!(resolver.anchors.contains_key("my_data"));
135///
136/// # Ok(())
137/// # }
138/// ```
139pub struct Resolver {
140    // Stores resolved documents by their absolute path
141    resolved_documents: HashMap<PathBuf, MonDocument>,
142    // Stack to detect circular dependencies during import resolution
143    resolving_stack: Vec<(PathBuf, Option<ImportStatement>)>,
144    // Global symbol table for types
145    pub symbol_table: AstSymbolTable,
146    // Global map for anchors
147    pub anchors: HashMap<String, MonValue>,
148
149    builtin_schemas_path: PathBuf,
150}
151
152impl Resolver {
153    /// Creates a new `Resolver` with a default search path for built-in schemas.
154    ///
155    /// The default path is determined by [`Resolver::default_builtin_path`].
156    #[must_use]
157    pub fn new() -> Self {
158        Self::with_builtin_path(Self::default_builtin_path())
159    }
160
161    /// Creates a new `Resolver` with a custom search path for built-in schemas.
162    ///
163    /// This is useful for testing or for applications that bundle their own schemas.
164    pub fn with_builtin_path(path: PathBuf) -> Self {
165        Resolver {
166            resolved_documents: HashMap::new(),
167            resolving_stack: Vec::new(),
168            symbol_table: AstSymbolTable::new(),
169            anchors: HashMap::new(),
170            builtin_schemas_path: path,
171        }
172    }
173
174    /// Determines the default path for built-in schemas, used for resolving `mon:` URIs.
175    ///
176    /// The lookup order is as follows:
177    /// 1. The `MON_BUILTIN_PATH` environment variable.
178    /// 2. The `.mon/schemas` directory in the user's home directory.
179    /// 3. The system-wide path `/usr/share/mon/schemas` (on Unix-like systems).
180    /// 4. Fallback to the current working directory (a warning will be logged).
181    fn default_builtin_path() -> PathBuf {
182        // Try these in order:
183        // 1. Environment variable
184        if let Ok(path) = std::env::var("MON_BUILTIN_PATH") {
185            return PathBuf::from(path);
186        }
187
188        // 2. User home directory
189        if let Some(home) = std::env::var_os("HOME") {
190            let user_schemas = PathBuf::from(home).join(".mon/schemas");
191            if user_schemas.exists() {
192                return user_schemas;
193            }
194        }
195
196        // 3. System-wide (Linux/Mac)
197        #[cfg(unix)]
198        {
199            let system_path = PathBuf::from("/usr/share/mon/schemas");
200            if system_path.exists() {
201                return system_path;
202            }
203        }
204
205        // 4. Fallback to current directory
206        warn!(
207            "Could not determine default schemas path, defaulting to current directory.
208        This is probably not intended"
209        );
210        PathBuf::from(".")
211    }
212
213    /// Resolves an import path string into an absolute `PathBuf`.
214    ///
215    /// This handles two types of paths:
216    /// - **mon-URIs**: Paths starting with `mon:` are resolved relative to the `builtin_schemas_path`.
217    ///   For example, `mon:types/linter` becomes `{builtin_schemas_path}/types/linter.mon`.
218    /// - **File Paths**: Relative paths are joined with the `current_dir`. Absolute paths are used as is.
219    fn resolve_import_path(&self, import_path: &str, current_dir: &Path) -> PathBuf {
220        // Handle mon: URI
221        if let Some(builtin_path) = import_path.strip_prefix("mon:") {
222            // "mon:types/linter" → "{builtin_schemas_path}/types/linter.mon"
223            return self
224                .builtin_schemas_path
225                .join(builtin_path)
226                .with_extension("mon");
227        }
228
229        // Handle relative/absolute paths normally
230        current_dir.join(import_path)
231    }
232    /// Recursively resolves a [`MonDocument`].
233    ///
234    /// This is the main entry point for the resolver's logic. It takes a parsed document
235    /// and performs the full semantic analysis lifecycle.
236    ///
237    /// # Arguments
238    ///
239    /// * `document`: The parsed [`MonDocument`] to resolve.
240    /// * `source_text`: The original source code string, used for error reporting.
241    /// * `file_path`: The absolute path to the file being resolved, used for resolving relative imports.
242    /// * `causing_import`: An optional [`ImportStatement`] that triggered the resolution of this file,
243    ///   used for circular dependency tracking.
244    ///
245    /// # Errors
246    ///
247    /// This function can return a [`ResolverError`] if any of the following occurs:
248    /// - A module specified in an `import` statement cannot be found ([`ResolverError::ModuleNotFound`]).
249    /// - A circular dependency is detected between imported modules ([`ResolverError::CircularDependency`]).
250    /// - An anchor, alias, or spread is used incorrectly.
251    /// - A validation error occurs when checking data against a schema ([`ResolverError::Validation`]).
252    /// - A parsing error occurs in an imported file.
253    pub fn resolve(
254        &mut self,
255        document: MonDocument,
256        source_text: &str,
257        file_path: PathBuf,
258        causing_import: Option<ImportStatement>,
259    ) -> Result<MonDocument, ResolverError> {
260        // Add the current file to the resolving stack to detect cycles
261        if let Some((_, Some(existing_causing_import))) =
262            self.resolving_stack.iter().find(|(p, _)| p == &file_path)
263        {
264            let cycle_str = self
265                .resolving_stack
266                .iter()
267                .map(|(p, _)| p.to_string_lossy().to_string())
268                .collect::<Vec<String>>()
269                .join(" -> ");
270            return Err(ResolverError::CircularDependency {
271                cycle: format!("{} -> {}", cycle_str, file_path.to_string_lossy()),
272                src: NamedSource::new(file_path.to_string_lossy(), source_text.to_string()).into(),
273                span: (
274                    existing_causing_import.pos_start,
275                    existing_causing_import.pos_end - existing_causing_import.pos_start,
276                )
277                    .into(),
278            });
279        }
280        self.resolving_stack
281            .push((file_path.clone(), causing_import)); // Push with the provided causing_import
282
283        // 1. Process imports
284        let current_dir = file_path.parent().unwrap_or_else(|| Path::new("."));
285        let source_arc = Arc::new(source_text.to_string());
286        for import_statement in &document.imports {
287            let imported_path_str = import_statement.path.trim_matches('"');
288
289            // for mon: ...
290            let absolute_imported_path = self.resolve_import_path(imported_path_str, current_dir);
291            if self
292                .resolved_documents
293                .contains_key(&absolute_imported_path)
294            {
295                continue;
296            }
297            let imported_source_text =
298                std::fs::read_to_string(&absolute_imported_path).map_err(|_| {
299                    ResolverError::ModuleNotFound {
300                        path: imported_path_str.to_string(),
301                        src: Arc::from(NamedSource::new(
302                            file_path.to_string_lossy(),
303                            source_arc.to_string(),
304                        )),
305                        span: (
306                            import_statement.pos_start,
307                            import_statement.pos_end - import_statement.pos_start,
308                        )
309                            .into(),
310                    }
311                })?;
312            // Parse without std_path - parser doesn't need it!
313            let mut parser = crate::parser::Parser::new_with_name(
314                &imported_source_text,
315                absolute_imported_path.to_string_lossy().to_string(),
316            )?;
317
318            let imported_document = parser.parse_document()?;
319            let resolved_imported_document = self.resolve(
320                imported_document,
321                &imported_source_text,
322                absolute_imported_path.clone(),
323                Some(import_statement.clone()),
324            )?;
325            self.resolved_documents
326                .insert(absolute_imported_path, resolved_imported_document);
327        }
328
329        // After resolving all imports, process named imports to populate the symbol table
330        for import_statement in &document.imports {
331            if let ImportSpec::Named(specifiers) = &import_statement.spec {
332                let imported_path_str = import_statement.path.trim_matches('"');
333                let absolute_imported_path = current_dir.join(imported_path_str);
334                if let Some(imported_doc) = self.resolved_documents.get(&absolute_imported_path) {
335                    if let MonValueKind::Object(members) = &imported_doc.root.kind {
336                        for specifier in specifiers {
337                            if !specifier.is_anchor {
338                                for member in members {
339                                    if let Member::TypeDefinition(td) = member {
340                                        if td.name == specifier.name {
341                                            self.symbol_table
342                                                .types
343                                                .insert(specifier.name.clone(), td.clone());
344                                        }
345                                    }
346                                }
347                            }
348                        }
349                    }
350                }
351            }
352        }
353
354        // 2. Collect type definitions and anchors from the current document
355        if let MonValueKind::Object(members) = &document.root.kind {
356            for member in members {
357                match member {
358                    Member::TypeDefinition(type_def) => {
359                        self.symbol_table
360                            .types
361                            .insert(type_def.name.clone(), type_def.clone());
362                    }
363                    Member::Pair(pair) => {
364                        if let Some(anchor_name) = &pair.value.anchor {
365                            self.anchors.insert(anchor_name.clone(), pair.value.clone());
366                        }
367                    }
368                    _ => {}
369                }
370            }
371        }
372
373        // 3. Resolve aliases and spreads
374        let resolved_root = self.resolve_value(document.root, &file_path, source_text)?;
375
376        // 4. Validate the resolved document
377        // This will involve iterating through the resolved_root and applying validations
378        // where `:: Type` is specified.
379        let final_resolved_root =
380            self.validate_document_root(resolved_root, &document.imports, &file_path, source_text)?;
381
382        let resolved_doc = MonDocument {
383            root: final_resolved_root,
384            imports: document.imports, // Imports are already processed
385        };
386
387        // Remove the current file from the stack
388        self.resolving_stack.pop();
389
390        Ok(resolved_doc)
391    }
392    // Helper function to recursively resolve aliases and spreads within a MonValue
393    /// Recursively resolves aliases and spreads within a [`MonValue`].
394    fn resolve_value(
395        &mut self,
396        mut value: MonValue,
397        file_path: &PathBuf,
398        source_text: &str,
399    ) -> Result<MonValue, ResolverError> {
400        let alias_span = value.get_source_span();
401
402        match &mut value.kind {
403            MonValueKind::Alias(alias_name) => {
404                // Resolve alias: find the anchored value and return a deep copy
405                let anchor_value = self.anchors.get(alias_name).ok_or_else(|| {
406                    // TODO: Get actual span for the alias
407                    ResolverError::AnchorNotFound {
408                        name: alias_name.clone(),
409                        src: Arc::from(NamedSource::new(
410                            file_path.to_string_lossy(),
411                            source_text.to_string(),
412                        )),
413                        span: alias_span,
414                    }
415                })?;
416                Ok(anchor_value.clone()) // Return a deep copy
417            }
418            MonValueKind::Object(members) => {
419                let mut resolved_members = Vec::new();
420                for member in members.drain(..) {
421                    match member {
422                        Member::Spread(spread_name) => {
423                            // Resolve object spread: merge members from anchored object
424                            let anchor_value = self.anchors.get(&spread_name).ok_or_else(|| {
425                                // TODO: Get actual span for the spread
426                                ResolverError::AnchorNotFound {
427                                    name: spread_name.clone(),
428                                    src: Arc::from(NamedSource::new(
429                                        file_path.to_string_lossy(),
430                                        source_text.to_string(),
431                                    )),
432                                    span: alias_span,
433                                }
434                            })?;
435                            if let MonValueKind::Object(spread_members) = &anchor_value.kind {
436                                let spread_members_clone = spread_members.clone();
437                                for spread_member in spread_members_clone {
438                                    // Recursively resolve spread members
439                                    resolved_members.push(self.resolve_value_member(
440                                        spread_member,
441                                        file_path,
442                                        source_text,
443                                    )?);
444                                }
445                            } else {
446                                return Err(ResolverError::SpreadOnNonObject {
447                                    name: spread_name.clone(),
448                                    src: Arc::from(NamedSource::new(
449                                        file_path.to_string_lossy(),
450                                        source_text.to_string(),
451                                    )),
452                                    span: alias_span,
453                                });
454                            }
455                        }
456                        _ => {
457                            // Recursively resolve other members
458                            resolved_members.push(self.resolve_value_member(
459                                member,
460                                file_path,
461                                source_text,
462                            )?);
463                        }
464                    }
465                }
466                // Handle key overriding for object spreads (local keys win)
467                let mut final_members_map: HashMap<String, Member> = HashMap::new();
468                for member in resolved_members {
469                    if let Member::Pair(pair) = member {
470                        final_members_map.insert(pair.key.clone(), Member::Pair(pair));
471                    } else {
472                        // Non-pair members (like TypeDefinition) are just added
473                        // This might need refinement depending on how TypeDefinitions are handled after resolution
474                        final_members_map.insert(format!("{member:?}"), member);
475                        // Dummy key for now
476                    }
477                }
478                let final_members = final_members_map.into_values().collect();
479                Ok(MonValue {
480                    kind: MonValueKind::Object(final_members),
481                    anchor: value.anchor,
482                    pos_start: value.pos_start,
483                    pos_end: value.pos_end,
484                })
485            }
486            MonValueKind::Array(elements) => {
487                let mut resolved_elements = Vec::new();
488                for element in elements.drain(..) {
489                    match element.kind {
490                        MonValueKind::ArraySpread(spread_name) => {
491                            // Resolve array spread: concatenate elements from anchored array
492                            let anchor_value = self.anchors.get(&spread_name).ok_or_else(|| {
493                                // TODO: Get actual span for the spread
494                                ResolverError::AnchorNotFound {
495                                    name: spread_name.clone(),
496                                    src: Arc::from(NamedSource::new(
497                                        file_path.to_string_lossy(),
498                                        source_text.to_string(),
499                                    )),
500                                    span: alias_span,
501                                }
502                            })?;
503                            if let MonValueKind::Array(spread_elements) = &anchor_value.kind {
504                                let spread_elements_clone = spread_elements.clone();
505                                for spread_element in spread_elements_clone {
506                                    // Recursively resolve spread elements
507                                    resolved_elements.push(self.resolve_value(
508                                        spread_element,
509                                        file_path,
510                                        source_text,
511                                    )?);
512                                }
513                            } else {
514                                // TODO: Get actual span for the spread
515                                return Err(ResolverError::SpreadOnNonArray {
516                                    name: spread_name.clone(),
517                                    src: Arc::from(NamedSource::new(
518                                        file_path.to_string_lossy(),
519                                        source_text.to_string(),
520                                    )),
521                                    span: alias_span,
522                                });
523                            }
524                        }
525                        _ => {
526                            // Recursively resolve other elements
527                            resolved_elements.push(self.resolve_value(
528                                element,
529                                file_path,
530                                source_text,
531                            )?);
532                        }
533                    }
534                }
535                Ok(MonValue {
536                    kind: MonValueKind::Array(resolved_elements),
537                    anchor: value.anchor,
538                    pos_start: value.pos_start,
539                    pos_end: value.pos_end,
540                })
541            }
542            _ => Ok(value), // Other literal values don't need further resolution
543        }
544    }
545
546    /// Helper to resolve a [`Member`] within a value.
547    fn resolve_value_member(
548        &mut self,
549        mut member: Member,
550        file_path: &PathBuf,
551        source_text: &str,
552    ) -> Result<Member, ResolverError> {
553        match &mut member {
554            Member::Pair(pair) => {
555                pair.value = self.resolve_value(pair.value.clone(), file_path, source_text)?;
556                Ok(member)
557            }
558            // Type definitions and imports are already processed or don't need further resolution here
559            _ => Ok(member),
560        }
561    }
562
563    /// Kicks off the validation process for the document's root value.
564    fn validate_document_root(
565        &mut self,
566        mut root_value: MonValue,
567        imports: &[ImportStatement], // Change this parameter
568        file_path: &PathBuf,
569        source_text: &str,
570    ) -> Result<MonValue, ResolverError> {
571        if let MonValueKind::Object(members) = &mut root_value.kind {
572            for member in members.iter_mut() {
573                if let Member::Pair(pair) = member {
574                    if let Some(type_spec) = &pair.validation {
575                        // Perform validation for this pair
576                        self.validate_value(
577                            &mut pair.value,
578                            type_spec,
579                            &pair.key,
580                            imports, // Pass the imports here
581                            file_path,
582                            source_text,
583                        )?;
584                    }
585                }
586            }
587        }
588        Ok(root_value)
589    }
590
591    /// Recursively validates a [`MonValue`] against a [`TypeSpec`].
592    fn validate_value(
593        &mut self,
594        value: &mut MonValue,
595        type_spec: &TypeSpec,
596        field_name: &str,            // For error reporting
597        imports: &[ImportStatement], // Change this parameter
598        file_path: &PathBuf,
599        source_text: &str,
600    ) -> Result<(), ResolverError> {
601        match type_spec {
602            TypeSpec::Simple(type_name, _) => {
603                // Handle built-in types and user-defined types (structs/enums)
604                match type_name.as_str() {
605                    "String" => {
606                        if !matches!(value.kind, MonValueKind::String(_)) {
607                            return Err(ResolverError::Validation(ValidationError::TypeMismatch {
608                                field_name: field_name.to_string(),
609                                expected_type: "String".to_string(),
610                                found_type: format!("{:?}", value.kind),
611                                src: Arc::from(NamedSource::new(
612                                    file_path.to_string_lossy(),
613                                    source_text.to_string(),
614                                )),
615                                span: (value.pos_start, value.pos_end - value.pos_start).into(),
616                            }));
617                        }
618                    }
619                    "Number" => {
620                        if !matches!(value.kind, MonValueKind::Number(_)) {
621                            return Err(ResolverError::Validation(ValidationError::TypeMismatch {
622                                field_name: field_name.to_string(),
623                                expected_type: "Number".to_string(),
624                                found_type: format!("{:?}", value.kind),
625                                src: Arc::from(NamedSource::new(
626                                    file_path.to_string_lossy(),
627                                    source_text.to_string(),
628                                )),
629                                span: (value.pos_start, value.pos_end - value.pos_start).into(),
630                            }));
631                        }
632                    }
633                    "Boolean" => {
634                        if !matches!(value.kind, MonValueKind::Boolean(_)) {
635                            return Err(ResolverError::Validation(ValidationError::TypeMismatch {
636                                field_name: field_name.to_string(),
637                                expected_type: "Boolean".to_string(),
638                                found_type: format!("{:?}", value.kind),
639                                src: Arc::from(NamedSource::new(
640                                    file_path.to_string_lossy(),
641                                    source_text.to_string(),
642                                )),
643                                span: (value.pos_start, value.pos_end - value.pos_start).into(),
644                            }));
645                        }
646                    }
647                    "Null" => {
648                        if !matches!(value.kind, MonValueKind::Null) {
649                            return Err(ResolverError::Validation(ValidationError::TypeMismatch {
650                                field_name: field_name.to_string(),
651                                expected_type: "Null".to_string(),
652                                found_type: format!("{:?}", value.kind),
653                                src: Arc::from(NamedSource::new(
654                                    file_path.to_string_lossy(),
655                                    source_text.to_string(),
656                                )),
657                                span: (value.pos_start, value.pos_end - value.pos_start).into(),
658                            }));
659                        }
660                    }
661                    "Object" => {
662                        if !matches!(value.kind, MonValueKind::Object(_)) {
663                            return Err(ResolverError::Validation(ValidationError::TypeMismatch {
664                                field_name: field_name.to_string(),
665                                expected_type: "Object".to_string(),
666                                found_type: format!("{:?}", value.kind),
667                                src: Arc::from(NamedSource::new(
668                                    file_path.to_string_lossy(),
669                                    source_text.to_string(),
670                                )),
671                                span: (value.pos_start, value.pos_end - value.pos_start).into(),
672                            }));
673                        }
674                    }
675                    "Array" => {
676                        if !matches!(value.kind, MonValueKind::Array(_)) {
677                            return Err(ResolverError::Validation(ValidationError::TypeMismatch {
678                                field_name: field_name.to_string(),
679                                expected_type: "Array".to_string(),
680                                found_type: format!("{:?}", value.kind),
681                                src: Arc::from(NamedSource::new(
682                                    file_path.to_string_lossy(),
683                                    source_text.to_string(),
684                                )),
685                                span: (value.pos_start, value.pos_end - value.pos_start).into(),
686                            }));
687                        }
688                    }
689                    "Any" => { /* Always valid, like you :D */ }
690                    _ => {
691                        // User-defined type (Struct or Enum)
692                        let (namespace, type_name_part) =
693                            if let Some((ns, tn)) = type_name.split_once('.') {
694                                (Some(ns), tn)
695                            } else {
696                                (None, type_name.as_str())
697                            };
698
699                        let type_def = if let Some(namespace) = namespace {
700                            // Find the import statement for this namespace
701                            let import_statement = imports
702                                .iter()
703                                .find(|i| {
704                                    if let ImportSpec::Namespace(ns) = &i.spec {
705                                        ns == namespace
706                                    } else {
707                                        false
708                                    }
709                                })
710                                .ok_or_else(|| {
711                                    ResolverError::Validation(ValidationError::UndefinedType {
712                                        type_name: type_name.clone(),
713                                        src: Arc::from(NamedSource::new(
714                                            file_path.to_string_lossy(),
715                                            source_text.to_string(),
716                                        )),
717                                        span: (value.pos_start, value.pos_end - value.pos_start)
718                                            .into(),
719                                    })
720                                })?;
721
722                            let imported_path_str = import_statement.path.trim_matches('"');
723                            let parent_dir = file_path.parent().ok_or_else(|| {
724                                // This case is unlikely but good to handle.
725                                // It means the file path is something like "/" or "C:\"
726                                ResolverError::ModuleNotFound {
727                                    path: import_statement.path.clone(),
728                                    src: Arc::from(NamedSource::new(
729                                        file_path.to_string_lossy(),
730                                        source_text.to_string(),
731                                    )),
732                                    span: (
733                                        import_statement.pos_start,
734                                        import_statement.pos_end - import_statement.pos_start,
735                                    )
736                                        .into(),
737                                }
738                            })?;
739                            let absolute_imported_path = parent_dir.join(imported_path_str);
740
741                            let imported_doc = self
742                                .resolved_documents
743                                .get(&absolute_imported_path)
744                                .ok_or_else(|| {
745                                    // This indicates a logic error in the resolver, as the document
746                                    // should have been resolved and stored during the initial import pass.
747                                    ResolverError::ModuleNotFound {
748                                        path: absolute_imported_path.to_string_lossy().to_string(),
749                                        src: Arc::from(NamedSource::new(
750                                            file_path.to_string_lossy(),
751                                            source_text.to_string(),
752                                        )),
753                                        span: (value.pos_start, value.pos_end - value.pos_start)
754                                            .into(),
755                                    }
756                                })?;
757
758                            if let MonValueKind::Object(members) = &imported_doc.root.kind {
759                                members.iter().find_map(|m| {
760                                    if let Member::TypeDefinition(td) = m {
761                                        if td.name == type_name_part {
762                                            return Some(td.def_type.clone());
763                                        }
764                                    }
765                                    None
766                                })
767                            } else {
768                                None
769                            }
770                        } else {
771                            self.symbol_table
772                                .types
773                                .get(type_name_part)
774                                .map(|td| td.def_type.clone())
775                        };
776
777                        if let Some(type_def) = type_def {
778                            match type_def {
779                                TypeDef::Struct(struct_def) => {
780                                    // Validate against struct
781                                    if let MonValueKind::Object(value_members) = &mut value.kind {
782                                        let mut value_map: HashMap<String, &mut MonValue> =
783                                            HashMap::new();
784                                        for member in value_members.iter_mut() {
785                                            if let Member::Pair(pair) = member {
786                                                value_map.insert(pair.key.clone(), &mut pair.value);
787                                            }
788                                        }
789
790                                        let mut new_members = Vec::new();
791                                        for field_def in &struct_def.fields {
792                                            if let Some(field_value) =
793                                                value_map.get_mut(&field_def.name)
794                                            {
795                                                // Field exists, validate its type
796                                                self.validate_value(
797                                                    field_value,
798                                                    &field_def.type_spec,
799                                                    &field_def.name,
800                                                    imports, // Pass the imports here
801                                                    file_path,
802                                                    source_text,
803                                                )?;
804                                            } else {
805                                                // Field missing
806                                                if field_def.default_value.is_none() {
807                                                    return Err(ResolverError::Validation(
808                                                        ValidationError::MissingField {
809                                                            field_name: field_def.name.clone(),
810                                                            struct_name: type_name.clone(),
811                                                            src: Arc::from(NamedSource::new(
812                                                                file_path.to_string_lossy(),
813                                                                source_text.to_string(),
814                                                            )),
815                                                            span: (
816                                                                value.pos_start,
817                                                                value.pos_end - value.pos_start,
818                                                            )
819                                                                .into(),
820                                                        },
821                                                    ));
822                                                }
823                                                // Field is missing, but has a default value.
824                                                // We need to insert it into the object.
825                                                if let Some(default_value) =
826                                                    &field_def.default_value
827                                                {
828                                                    new_members.push(Member::Pair(
829                                                        crate::ast::Pair {
830                                                            key: field_def.name.clone(),
831                                                            value: default_value.clone(),
832                                                            validation: None,
833                                                        },
834                                                    ));
835                                                }
836                                            }
837                                        }
838                                        value_members.extend(new_members);
839
840                                        // Check for extra fields
841                                        for member in value_members.iter() {
842                                            if let Member::Pair(pair) = member {
843                                                if !struct_def
844                                                    .fields
845                                                    .iter()
846                                                    .any(|f| f.name == pair.key)
847                                                {
848                                                    return Err(ResolverError::Validation(
849                                                        ValidationError::UnexpectedField {
850                                                            field_name: pair.key.clone(),
851                                                            struct_name: type_name.clone(),
852                                                            src: Arc::from(NamedSource::new(
853                                                                file_path.to_string_lossy(),
854                                                                source_text.to_string(),
855                                                            )),
856                                                            span: (
857                                                                value.pos_start,
858                                                                value.pos_end - value.pos_start,
859                                                            )
860                                                                .into(),
861                                                        },
862                                                    ));
863                                                }
864                                            }
865                                        }
866                                    } else {
867                                        return Err(ResolverError::Validation(
868                                            ValidationError::TypeMismatch {
869                                                field_name: field_name.to_string(),
870                                                expected_type: type_name.clone(),
871                                                found_type: format!("{:?}", value.kind),
872                                                src: Arc::from(NamedSource::new(
873                                                    file_path.to_string_lossy(),
874                                                    source_text.to_string(),
875                                                )),
876                                                span: (
877                                                    value.pos_start,
878                                                    value.pos_end - value.pos_start,
879                                                )
880                                                    .into(),
881                                            },
882                                        ));
883                                    }
884                                }
885                                TypeDef::Enum(enum_def) => {
886                                    // Validate against enum
887                                    if let MonValueKind::EnumValue {
888                                        enum_name,
889                                        variant_name,
890                                    } = &value.kind
891                                    {
892                                        if enum_name != type_name {
893                                            return Err(ResolverError::Validation(
894                                                ValidationError::TypeMismatch {
895                                                    field_name: field_name.to_string(),
896                                                    expected_type: format!("enum {}", type_name),
897                                                    found_type: format!("enum {}", enum_name),
898                                                    src: Arc::from(NamedSource::new(
899                                                        file_path.to_string_lossy(),
900                                                        source_text.to_string(),
901                                                    )),
902                                                    span: (
903                                                        value.pos_start,
904                                                        value.pos_end - value.pos_start,
905                                                    )
906                                                        .into(),
907                                                },
908                                            ));
909                                        }
910                                        if !enum_def.variants.contains(variant_name) {
911                                            return Err(ResolverError::Validation(
912                                                ValidationError::UndefinedEnumVariant {
913                                                    variant_name: variant_name.clone(),
914                                                    enum_name: type_name.clone(),
915                                                    src: Arc::from(NamedSource::new(
916                                                        file_path.to_string_lossy(),
917                                                        source_text.to_string(),
918                                                    )),
919                                                    span: (
920                                                        value.pos_start,
921                                                        value.pos_end - value.pos_start,
922                                                    )
923                                                        .into(),
924                                                },
925                                            ));
926                                        }
927                                    } else {
928                                        return Err(ResolverError::Validation(
929                                            ValidationError::TypeMismatch {
930                                                field_name: field_name.to_string(),
931                                                expected_type: format!("enum {}", type_name),
932                                                found_type: format!("{:?}", value.kind),
933                                                src: Arc::from(NamedSource::new(
934                                                    file_path.to_string_lossy(),
935                                                    source_text.to_string(),
936                                                )),
937                                                span: (
938                                                    value.pos_start,
939                                                    value.pos_end - value.pos_start,
940                                                )
941                                                    .into(),
942                                            },
943                                        ));
944                                    }
945                                }
946                            }
947                        } else {
948                            return Err(ResolverError::Validation(
949                                ValidationError::UndefinedType {
950                                    type_name: type_name.clone(),
951                                    src: Arc::from(NamedSource::new(
952                                        file_path.to_string_lossy(),
953                                        source_text.to_string(),
954                                    )),
955                                    span: (value.pos_start, value.pos_end - value.pos_start).into(),
956                                },
957                            ));
958                        }
959                    }
960                }
961            }
962            TypeSpec::Collection(collection_types, _) => {
963                // Handle array validation
964                if let MonValueKind::Array(elements) = &mut value.kind {
965                    self.validate_collection(
966                        elements,
967                        collection_types,
968                        field_name,
969                        imports, // Pass the imports here
970                        file_path,
971                        source_text,
972                    )?;
973                } else {
974                    return Err(ResolverError::Validation(ValidationError::TypeMismatch {
975                        field_name: field_name.to_string(),
976                        expected_type: "Array".to_string(),
977                        found_type: format!("{:?}", value.kind),
978                        src: Arc::from(NamedSource::new(
979                            file_path.to_string_lossy(),
980                            source_text.to_string(),
981                        )),
982                        span: (value.pos_start, value.pos_end - value.pos_start).into(),
983                    }));
984                }
985            }
986            TypeSpec::Spread(_, _) => {
987                // Spread types are handled during parsing/resolution, not validation directly
988                return Ok(());
989            }
990        }
991        Ok(())
992    }
993
994    /// Validates the elements of an array against a slice of collection `TypeSpec`s.
995    fn validate_collection(
996        &mut self,
997        elements: &mut [MonValue],
998        collection_types: &[TypeSpec],
999        field_name: &str,
1000        imports: &[ImportStatement], // Change this parameter
1001        file_path: &PathBuf,
1002        source_text: &str,
1003    ) -> Result<(), ResolverError> {
1004        // Case 1: [T] - Exactly one element of type T
1005        if collection_types.len() == 1 && !matches!(collection_types[0], TypeSpec::Spread(_, _)) {
1006            self.validate_value(
1007                &mut elements[0],
1008                &collection_types[0],
1009                field_name,
1010                imports, // Pass the imports here
1011                file_path,
1012                source_text,
1013            )?;
1014            return Ok(());
1015        }
1016
1017        // Case 2: [T...] - Zero or more elements of type T
1018        if collection_types.len() == 1 && matches!(collection_types[0], TypeSpec::Spread(_, _)) {
1019            if let TypeSpec::Spread(inner_type, _) = &collection_types[0] {
1020                for element in elements {
1021                    self.validate_value(
1022                        element,
1023                        inner_type,
1024                        field_name,
1025                        imports,
1026                        file_path,
1027                        source_text,
1028                    )?;
1029                }
1030                return Ok(());
1031            }
1032        }
1033
1034        // Case 3: Tuple-like [T1, T2, ...]
1035        let has_spread = collection_types
1036            .iter()
1037            .any(|t| matches!(t, TypeSpec::Spread(_, _)));
1038        if !has_spread {
1039            if elements.len() != collection_types.len() {
1040                // TODO: Better error for wrong number of elements
1041                return Err(ResolverError::Validation(ValidationError::TypeMismatch {
1042                    field_name: field_name.to_string(),
1043                    expected_type: format!("tuple with {} elements", collection_types.len()),
1044                    found_type: format!("tuple with {} elements", elements.len()),
1045                    src: Arc::from(NamedSource::new(
1046                        file_path.to_string_lossy(),
1047                        source_text.to_string(),
1048                    )),
1049                    span: (
1050                        elements.first().map_or(0, |e| e.pos_start),
1051                        elements.last().map_or(0, |e| e.pos_end)
1052                            - elements.first().map_or(0, |e| e.pos_start),
1053                    )
1054                        .into(),
1055                }));
1056            }
1057            for (i, element) in elements.iter_mut().enumerate() {
1058                self.validate_value(
1059                    element,
1060                    &collection_types[i],
1061                    field_name,
1062                    imports, // Pass the imports here
1063                    file_path,
1064                    source_text,
1065                )?;
1066            }
1067            return Ok(());
1068        }
1069
1070        // Case 4: [T1, T2...] - One or more elements, first is T1, rest are T2
1071        if collection_types.len() == 2
1072            && !matches!(collection_types[0], TypeSpec::Spread(_, _))
1073            && matches!(collection_types[1], TypeSpec::Spread(_, _))
1074        {
1075            if elements.is_empty() {
1076                return Err(ResolverError::Validation(ValidationError::TypeMismatch {
1077                    field_name: field_name.to_string(),
1078                    expected_type: "array with at least 1 element".to_string(),
1079                    found_type: "empty array".to_string(),
1080                    src: Arc::from(NamedSource::new(
1081                        file_path.to_string_lossy(),
1082                        source_text.to_string(),
1083                    )),
1084                    span: (
1085                        elements.first().map_or(0, |e| e.pos_start),
1086                        elements.last().map_or(0, |e| e.pos_end)
1087                            - elements.first().map_or(0, |e| e.pos_start),
1088                    )
1089                        .into(),
1090                }));
1091            }
1092            self.validate_value(
1093                &mut elements[0],
1094                &collection_types[0],
1095                field_name,
1096                imports, // Pass the imports here
1097                file_path,
1098                source_text,
1099            )?;
1100            if let TypeSpec::Spread(inner_type, _) = &collection_types[1] {
1101                for element in &mut elements[1..] {
1102                    self.validate_value(
1103                        element,
1104                        inner_type,
1105                        field_name,
1106                        imports,
1107                        file_path,
1108                        source_text,
1109                    )?;
1110                }
1111            }
1112            return Ok(());
1113        }
1114
1115        // Case 5: [T1..., T2] - One or more elements, last is T2, rest are T1
1116        if collection_types.len() == 2
1117            && matches!(collection_types[0], TypeSpec::Spread(_, _))
1118            && !matches!(collection_types[1], TypeSpec::Spread(_, _))
1119        {
1120            if elements.is_empty() {
1121                return Err(ResolverError::Validation(ValidationError::TypeMismatch {
1122                    field_name: field_name.to_string(),
1123                    expected_type: "array with at least 1 element".to_string(),
1124                    found_type: "empty array".to_string(),
1125                    src: Arc::from(NamedSource::new(
1126                        file_path.to_string_lossy(),
1127                        source_text.to_string(),
1128                    )),
1129                    span: (
1130                        elements.first().map_or(0, |e| e.pos_start),
1131                        elements.last().map_or(0, |e| e.pos_end)
1132                            - elements.first().map_or(0, |e| e.pos_start),
1133                    )
1134                        .into(),
1135                }));
1136            }
1137            let (head, last) = elements.split_at_mut(elements.len() - 1);
1138            self.validate_value(
1139                last.first_mut().unwrap(), // Get the last element
1140                &collection_types[1],
1141                field_name,
1142                imports, // Pass the imports here
1143                file_path,
1144                source_text,
1145            )?;
1146            if let TypeSpec::Spread(inner_type, _) = &collection_types[0] {
1147                for element in head {
1148                    self.validate_value(
1149                        element,
1150                        inner_type,
1151                        field_name,
1152                        imports,
1153                        file_path,
1154                        source_text,
1155                    )?;
1156                }
1157            }
1158            return Ok(());
1159        }
1160
1161        // If none of the specific cases match, it's an unimplemented complex collection type
1162        Err(ResolverError::Validation(
1163            ValidationError::UnimplementedCollectionValidation {
1164                field_name: field_name.to_string(),
1165                src: Arc::from(NamedSource::new(
1166                    file_path.to_string_lossy(),
1167                    source_text.to_string(),
1168                )),
1169                span: (
1170                    elements.first().map_or(0, |e| e.pos_start),
1171                    elements.last().map_or(0, |e| e.pos_end)
1172                        - elements.first().map_or(0, |e| e.pos_start),
1173                )
1174                    .into(),
1175            },
1176        ))
1177    }
1178}
1179
1180impl Default for Resolver {
1181    fn default() -> Self {
1182        Self::new()
1183    }
1184}
1185
1186#[cfg(test)]
1187mod tests {
1188    use crate::parser::Parser;
1189    use crate::resolver::Resolver;
1190    use miette::Report;
1191    use std::fs;
1192    use std::path::{Path, PathBuf};
1193
1194    fn resolve_ok(source: &str, file_name: &str) -> crate::ast::MonDocument {
1195        let mut parser = Parser::new_with_name(source, file_name.to_string()).unwrap();
1196        let document = parser.parse_document().unwrap();
1197        let mut resolver = Resolver::new();
1198        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1199        path.push(file_name);
1200        match resolver.resolve(document, source, path, None) {
1201            Ok(doc) => doc,
1202            Err(err) => {
1203                let report = Report::from(err);
1204                panic!("{report:#}");
1205            }
1206        }
1207    }
1208
1209    fn resolve_err(source: &str, file_name: &str) -> crate::error::ResolverError {
1210        let mut parser = Parser::new_with_name(source, file_name.to_string()).unwrap();
1211        let document = parser.parse_document().unwrap();
1212        let mut resolver = Resolver::new();
1213        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
1214        path.push(file_name);
1215        match resolver.resolve(document, source, path, None) {
1216            Ok(_) => panic!("Expected a ResolverError, but got Ok"),
1217            Err(err) => err,
1218        }
1219    }
1220
1221    #[test]
1222    fn test_simple_alias_resolution() {
1223        let source = r"{ &my_value: 123, alias_value: *my_value }";
1224        let doc = resolve_ok(source, "test.mon");
1225
1226        let root_object = match doc.root.kind {
1227            crate::ast::MonValueKind::Object(members) => members,
1228            _ => panic!("Expected an object"),
1229        };
1230
1231        // Check that the alias_value is resolved to 123
1232        let alias_member = root_object
1233            .iter()
1234            .find(|m| {
1235                if let crate::ast::Member::Pair(p) = m {
1236                    p.key == "alias_value"
1237                } else {
1238                    false
1239                }
1240            })
1241            .unwrap();
1242
1243        if let crate::ast::Member::Pair(p) = alias_member {
1244            assert_eq!(p.value.kind, crate::ast::MonValueKind::Number(123.0));
1245        } else {
1246            panic!("Expected a pair member");
1247        }
1248    }
1249
1250    #[test]
1251    fn test_object_spread_resolution() {
1252        let source = r#"{ 
1253        &base_config: { host: "localhost", port: 8080 },
1254        app_config: {
1255            ...*base_config,
1256            port: 9000, // Override
1257            debug: true,
1258        }
1259    }"#;
1260        let doc = resolve_ok(source, "test.mon");
1261
1262        let root_object = match doc.root.kind {
1263            crate::ast::MonValueKind::Object(members) => members,
1264            _ => panic!("Expected an object"),
1265        };
1266
1267        let app_config_member = root_object
1268            .iter()
1269            .find(|m| {
1270                if let crate::ast::Member::Pair(p) = m {
1271                    p.key == "app_config"
1272                } else {
1273                    false
1274                }
1275            })
1276            .unwrap();
1277
1278        if let crate::ast::Member::Pair(p) = app_config_member {
1279            let app_config_object = match &p.value.kind {
1280                crate::ast::MonValueKind::Object(members) => members,
1281                _ => panic!("Expected app_config to be an object"),
1282            };
1283
1284            // Check host
1285            let host_member = app_config_object
1286                .iter()
1287                .find(|m| {
1288                    if let crate::ast::Member::Pair(p) = m {
1289                        p.key == "host"
1290                    } else {
1291                        false
1292                    }
1293                })
1294                .unwrap();
1295            if let crate::ast::Member::Pair(p) = host_member {
1296                assert_eq!(
1297                    p.value.kind,
1298                    crate::ast::MonValueKind::String("localhost".to_string())
1299                );
1300            } else {
1301                panic!("Expected host to be a pair");
1302            }
1303
1304            // Check port (overridden)
1305            let port_member = app_config_object
1306                .iter()
1307                .find(|m| {
1308                    if let crate::ast::Member::Pair(p) = m {
1309                        p.key == "port"
1310                    } else {
1311                        false
1312                    }
1313                })
1314                .unwrap();
1315            if let crate::ast::Member::Pair(p) = port_member {
1316                assert_eq!(p.value.kind, crate::ast::MonValueKind::Number(9000.0));
1317            } else {
1318                panic!("Expected port to be a pair");
1319            }
1320
1321            // Check debug (new field)
1322            let debug_member = app_config_object
1323                .iter()
1324                .find(|m| {
1325                    if let crate::ast::Member::Pair(p) = m {
1326                        p.key == "debug"
1327                    } else {
1328                        false
1329                    }
1330                })
1331                .unwrap();
1332            if let crate::ast::Member::Pair(p) = debug_member {
1333                assert_eq!(p.value.kind, crate::ast::MonValueKind::Boolean(true));
1334            } else {
1335                panic!("Expected debug to be a pair");
1336            }
1337        } else {
1338            panic!("Expected app_config to be a pair member");
1339        }
1340    }
1341
1342    #[test]
1343    fn test_array_spread_resolution() {
1344        let source = r#"{
1345        &base_tags: ["tag1", "tag2"],
1346        item_tags: [
1347            "start",
1348            ...*base_tags,
1349            "end",
1350        ]
1351    }"#;
1352        let doc = resolve_ok(source, "test.mon");
1353
1354        let root_object = match doc.root.kind {
1355            crate::ast::MonValueKind::Object(members) => members,
1356            _ => panic!("Expected an object"),
1357        };
1358
1359        let item_tags_member = root_object
1360            .iter()
1361            .find(|m| {
1362                if let crate::ast::Member::Pair(p) = m {
1363                    p.key == "item_tags"
1364                } else {
1365                    false
1366                }
1367            })
1368            .unwrap();
1369
1370        if let crate::ast::Member::Pair(p) = item_tags_member {
1371            let item_tags_array = match &p.value.kind {
1372                crate::ast::MonValueKind::Array(elements) => elements,
1373                _ => panic!("Expected item_tags to be an array"),
1374            };
1375
1376            assert_eq!(item_tags_array.len(), 4);
1377            assert_eq!(
1378                item_tags_array[0].kind,
1379                crate::ast::MonValueKind::String("start".to_string())
1380            );
1381            assert_eq!(
1382                item_tags_array[1].kind,
1383                crate::ast::MonValueKind::String("tag1".to_string())
1384            );
1385            assert_eq!(
1386                item_tags_array[2].kind,
1387                crate::ast::MonValueKind::String("tag2".to_string())
1388            );
1389            assert_eq!(
1390                item_tags_array[3].kind,
1391                crate::ast::MonValueKind::String("end".to_string())
1392            );
1393        } else {
1394            panic!("Expected item_tags to be a pair member");
1395        }
1396    }
1397
1398    #[test]
1399    fn test_struct_validation_with_defaults_and_collections_ok() {
1400        let source = r###"
1401        {
1402            User: #struct {
1403                id(Number),
1404                name(String),
1405                email(String) = "default@example.com",
1406                is_active(Boolean) = true,
1407                roles([String...]),
1408                permissions([String, Number]),
1409                log_data([String, Any...]),
1410                status_history([Boolean..., String]),
1411            },
1412
1413            // Valid user with defaults
1414            user1 :: User = {
1415                id: 1,
1416                name: "Alice",
1417                roles: ["admin", "editor"],
1418                permissions: ["read", 1],
1419                log_data: ["login", { timestamp: "...", ip: "..." }],
1420                status_history: [true, false, "active"],
1421            },
1422
1423            // Valid user, omitting optional fields
1424            user2 :: User = {
1425                id: 2,
1426                name: "Bob",
1427                roles: [],
1428                permissions: ["write", 2],
1429                log_data: ["logout"],
1430                status_history: ["inactive"],
1431            },
1432        }
1433    "###;
1434
1435        // Test valid user1
1436        let doc = resolve_ok(source, "test_validation.mon");
1437        let root_object = match doc.root.kind {
1438            crate::ast::MonValueKind::Object(members) => members,
1439            _ => panic!("Expected an object"),
1440        };
1441
1442        let user1_member = root_object
1443            .iter()
1444            .find(|m| {
1445                if let crate::ast::Member::Pair(p) = m {
1446                    p.key == "user1"
1447                } else {
1448                    false
1449                }
1450            })
1451            .unwrap();
1452
1453        if let crate::ast::Member::Pair(p) = user1_member {
1454            let user1_object = match &p.value.kind {
1455                crate::ast::MonValueKind::Object(members) => members,
1456                _ => panic!("Expected user1 to be an object"),
1457            };
1458
1459            // Check default email
1460            let email_member = user1_object
1461                .iter()
1462                .find(|m| {
1463                    if let crate::ast::Member::Pair(p) = m {
1464                        p.key == "email"
1465                    } else {
1466                        false
1467                    }
1468                })
1469                .unwrap();
1470            if let crate::ast::Member::Pair(p) = email_member {
1471                assert_eq!(
1472                    p.value.kind,
1473                    crate::ast::MonValueKind::String("default@example.com".to_string())
1474                );
1475            } else {
1476                panic!("Expected email to be a pair");
1477            }
1478
1479            // Check default is_active
1480            let is_active_member = user1_object
1481                .iter()
1482                .find(|m| {
1483                    if let crate::ast::Member::Pair(p) = m {
1484                        p.key == "is_active"
1485                    } else {
1486                        false
1487                    }
1488                })
1489                .unwrap();
1490            if let crate::ast::Member::Pair(p) = is_active_member {
1491                assert_eq!(p.value.kind, crate::ast::MonValueKind::Boolean(true));
1492            } else {
1493                panic!("Expected is_active to be a pair");
1494            }
1495        } else {
1496            panic!("Expected user1 to be a pair member");
1497        }
1498    }
1499
1500    #[test]
1501    fn test_struct_validation_missing_required_field() {
1502        let source = r###"
1503        {
1504            User: #struct { id(Number), name(String) },
1505            invalid_user :: User = { id: 3 },
1506        }
1507    "###;
1508        let err = resolve_err(source, "test_validation.mon");
1509        match err {
1510            crate::error::ResolverError::Validation(
1511                crate::error::ValidationError::MissingField { field_name, .. },
1512            ) => {
1513                assert_eq!(field_name, "name");
1514            }
1515            _ => panic!("Expected MissingField error, but got {err:?}"),
1516        }
1517    }
1518
1519    #[test]
1520    fn test_struct_validation_wrong_id_type() {
1521        let source = r###"
1522        {
1523            User: #struct { id(Number), name(String) },
1524            invalid_user :: User = { id: "four", name: "Charlie" },
1525        }
1526    "###;
1527        let err = resolve_err(source, "test_validation.mon");
1528        match err {
1529            crate::error::ResolverError::Validation(
1530                crate::error::ValidationError::TypeMismatch {
1531                    field_name,
1532                    expected_type,
1533                    found_type,
1534                    ..
1535                },
1536            ) => {
1537                assert_eq!(field_name, "id");
1538                assert_eq!(expected_type, "Number");
1539                assert!(found_type.contains("String"));
1540            }
1541            _ => panic!("Expected TypeMismatch error for id, but got {err:?}"),
1542        }
1543    }
1544
1545    #[test]
1546    fn test_struct_validation_unexpected_field() {
1547        let source = r###"
1548        {
1549            User: #struct { id(Number), name(String) },
1550            invalid_user :: User = { id: 5, name: "David", age: 30 },
1551        }
1552    "###;
1553        let err = resolve_err(source, "test_validation.mon");
1554        match err {
1555            crate::error::ResolverError::Validation(
1556                crate::error::ValidationError::UnexpectedField { field_name, .. },
1557            ) => {
1558                assert_eq!(field_name, "age");
1559            }
1560            _ => panic!("Expected UnexpectedField error, but got {err:?}"),
1561        }
1562    }
1563
1564    #[test]
1565    fn test_struct_validation_roles_type_mismatch() {
1566        let source = r###"
1567        {
1568            User: #struct { roles([String...]) },
1569            invalid_user :: User = { roles: ["viewer", 123] },
1570        }
1571    "###;
1572        let err = resolve_err(source, "test_validation.mon");
1573        match err {
1574            crate::error::ResolverError::Validation(
1575                crate::error::ValidationError::TypeMismatch {
1576                    field_name,
1577                    expected_type,
1578                    found_type,
1579                    ..
1580                },
1581            ) => {
1582                assert_eq!(field_name, "roles");
1583                assert_eq!(expected_type, "String");
1584                assert!(found_type.contains("Number"));
1585            }
1586            _ => panic!("Expected TypeMismatch error for roles, but got {err:?}"),
1587        }
1588    }
1589
1590    #[test]
1591    fn test_struct_validation_permissions_length_mismatch() {
1592        let source = r###"
1593        {
1594            User: #struct { permissions([String, Number]) },
1595            invalid_user :: User = { permissions: ["read"] },
1596        }
1597    "###;
1598        let err = resolve_err(source, "test_validation.mon");
1599        match err {
1600            crate::error::ResolverError::Validation(
1601                crate::error::ValidationError::TypeMismatch {
1602                    field_name,
1603                    expected_type,
1604                    found_type,
1605                    ..
1606                },
1607            ) => {
1608                assert_eq!(field_name, "permissions");
1609                assert!(expected_type.contains("tuple with 2 elements"));
1610                assert!(found_type.contains("tuple with 1 elements"));
1611            }
1612            _ => panic!("Expected TypeMismatch error for permissions length, but got {err:?}"),
1613        }
1614    }
1615
1616    #[test]
1617    fn test_struct_validation_permissions_type_mismatch() {
1618        let source = r###"
1619        {
1620            User: #struct { permissions([String, Number]) },
1621            invalid_user :: User = { permissions: [8, "write"] },
1622        }
1623    "###;
1624        let err = resolve_err(source, "test_validation.mon");
1625        match err {
1626            crate::error::ResolverError::Validation(
1627                crate::error::ValidationError::TypeMismatch {
1628                    field_name,
1629                    expected_type,
1630                    found_type,
1631                    ..
1632                },
1633            ) => {
1634                assert_eq!(field_name, "permissions");
1635                assert_eq!(expected_type, "String");
1636                assert!(found_type.contains("Number"));
1637            }
1638            _ => panic!("Expected TypeMismatch error for permissions types, but got {err:?}"),
1639        }
1640    }
1641
1642    #[test]
1643    fn test_struct_validation_log_data_first_type_mismatch() {
1644        let source = r###"
1645        {
1646            User: #struct { log_data([String, Any...]) },
1647            invalid_user :: User = { log_data: [123, "event"] },
1648        }
1649    "###;
1650        let err = resolve_err(source, "test_validation.mon");
1651        match err {
1652            crate::error::ResolverError::Validation(
1653                crate::error::ValidationError::TypeMismatch {
1654                    field_name,
1655                    expected_type,
1656                    found_type,
1657                    ..
1658                },
1659            ) => {
1660                assert_eq!(field_name, "log_data");
1661                assert_eq!(expected_type, "String");
1662                assert!(found_type.contains("Number"));
1663            }
1664            _ => panic!("Expected TypeMismatch error for log_data first type, but got {err:?}"),
1665        }
1666    }
1667
1668    #[test]
1669    fn test_struct_validation_status_history_last_type_mismatch() {
1670        let source = r###"
1671        {
1672            User: #struct { status_history([Boolean..., String]) },
1673            invalid_user :: User = { status_history: [true, 123] },
1674        }
1675    "###;
1676        let err = resolve_err(source, "test_validation.mon");
1677        match err {
1678            crate::error::ResolverError::Validation(
1679                crate::error::ValidationError::TypeMismatch {
1680                    field_name,
1681                    expected_type,
1682                    found_type,
1683                    ..
1684                },
1685            ) => {
1686                assert_eq!(field_name, "status_history");
1687                assert_eq!(expected_type, "String");
1688                assert!(found_type.contains("Number"));
1689            }
1690            _ => {
1691                panic!("Expected TypeMismatch error for status_history last type, but got {err:?}")
1692            }
1693        }
1694    }
1695
1696    #[test]
1697    fn test_nested_struct_validation_ok() {
1698        let source = r###"
1699        {
1700            Profile: #struct {
1701                username(String),
1702                email(String),
1703            },
1704            User: #struct {
1705                id(Number),
1706                profile(Profile),
1707            },
1708
1709            // Valid nested struct
1710            user1 :: User = {
1711                id: 1,
1712                profile: {
1713                    username: "alice",
1714                    email: "alice@example.com",
1715                },
1716            },
1717        }
1718    "###;
1719
1720        resolve_ok(source, "test_nested_ok.mon");
1721    }
1722
1723    #[test]
1724    fn test_nested_struct_validation_err() {
1725        let source = r###"
1726        {
1727            Profile: #struct {
1728                username(String),
1729                email(String),
1730            },
1731            User: #struct {
1732                id(Number),
1733                profile(Profile),
1734            },
1735
1736            // Invalid: Nested struct has wrong type for username
1737            user2 :: User = {
1738                id: 2,
1739                profile: {
1740                    username: 123,
1741                    email: "bob@example.com",
1742                },
1743            },
1744        }
1745    "###;
1746
1747        let err = resolve_err(source, "test_nested_err.mon");
1748        match err {
1749            crate::error::ResolverError::Validation(
1750                crate::error::ValidationError::TypeMismatch {
1751                    field_name,
1752                    expected_type,
1753                    found_type,
1754                    ..
1755                },
1756            ) => {
1757                assert_eq!(field_name, "username");
1758                assert_eq!(expected_type, "String");
1759                assert!(found_type.contains("Number"));
1760            }
1761            _ => panic!("Expected TypeMismatch error for username, but got {err:?}"),
1762        }
1763    }
1764
1765    #[test]
1766    fn test_cross_file_validation() {
1767        let source = fs::read_to_string("tests/cross_file_main.mon").unwrap();
1768        resolve_ok(&source, "tests/cross_file_main.mon");
1769    }
1770
1771    #[test]
1772    fn test_parser_for_schemas_file() {
1773        let source = fs::read_to_string("tests/cross_file_schemas.mon").unwrap();
1774        let mut parser = Parser::new_with_name(&source, "test.mon".to_string()).unwrap();
1775        let _ = parser.parse_document().unwrap();
1776    }
1777
1778    #[test]
1779    fn test_named_import_validation() {
1780        let source = fs::read_to_string("tests/named_import_main.mon").unwrap();
1781        resolve_ok(&source, "tests/named_import_main.mon");
1782    }
1783
1784    use super::*;
1785    use tempfile::TempDir;
1786    // Helper to create a test resolver with a custom builtin path
1787    fn test_resolver_with_builtin(builtin_path: PathBuf) -> Resolver {
1788        Resolver::with_builtin_path(builtin_path)
1789    }
1790    // Helper to create test files
1791    fn create_test_file(dir: &Path, name: &str, content: &str) -> PathBuf {
1792        let path = dir.join(name);
1793        fs::write(&path, content).unwrap();
1794        path
1795    }
1796    #[test]
1797    fn test_mon_uri_resolution() {
1798        let temp_dir = TempDir::new().unwrap();
1799        let builtin_path = temp_dir.path().join("schemas");
1800        fs::create_dir_all(builtin_path.join("types")).unwrap();
1801        // Create a builtin schema file
1802        create_test_file(
1803            &builtin_path.join("types"),
1804            "linter.mon",
1805            r#"{ 
1806                LintConfig: #struct {
1807                    max_depth(Number) = 5
1808                }
1809            }"#,
1810        );
1811        let resolver = test_resolver_with_builtin(builtin_path.clone());
1812        // Test mon: URI resolution
1813        let resolved = resolver.resolve_import_path("mon:types/linter", Path::new("."));
1814        assert_eq!(resolved, builtin_path.join("types/linter.mon"));
1815    }
1816    #[test]
1817    fn test_relative_path_unchanged() {
1818        let resolver = Resolver::new();
1819        let current_dir = Path::new("/project/src");
1820        // Relative paths should be resolved relative to current_dir
1821        let resolved = resolver.resolve_import_path("./config.mon", current_dir);
1822        assert_eq!(resolved, PathBuf::from("/project/src/./config.mon"));
1823        let resolved = resolver.resolve_import_path("../shared/types.mon", current_dir);
1824        assert_eq!(resolved, PathBuf::from("/project/src/../shared/types.mon"));
1825    }
1826    #[test]
1827    fn test_absolute_path_preserved() {
1828        let resolver = Resolver::new();
1829        let current_dir = Path::new("/project");
1830        let resolved = resolver.resolve_import_path("/usr/share/schemas/common.mon", current_dir);
1831        assert_eq!(resolved, PathBuf::from("/usr/share/schemas/common.mon"));
1832    }
1833    #[test]
1834    fn test_mon_uri_nested_paths() {
1835        let temp_dir = TempDir::new().unwrap();
1836        let builtin_path = temp_dir.path().join("schemas");
1837        fs::create_dir_all(builtin_path.join("types/database")).unwrap();
1838        let resolver = test_resolver_with_builtin(builtin_path.clone());
1839        // Test deeply nested paths
1840        let resolved = resolver.resolve_import_path("mon:types/database/postgres", Path::new("."));
1841        assert_eq!(resolved, builtin_path.join("types/database/postgres.mon"));
1842    }
1843    #[test]
1844    fn test_mon_uri_with_full_resolution() {
1845        let temp_dir = TempDir::new().unwrap();
1846        let builtin_path = temp_dir.path().join("schemas");
1847        fs::create_dir_all(builtin_path.join("types")).unwrap();
1848        // Create builtin schema
1849        let schema_content = r#"{ 
1850            TestType: #struct {
1851                field(String)
1852            }
1853        }"#;
1854        create_test_file(&builtin_path.join("types"), "test.mon", schema_content);
1855        // Create main file that imports from mon:
1856        let main_content = r###"
1857            import { TestType } from "mon:types/test"
1858
1859            {
1860                value :: TestType = { field: "hello" }
1861            }
1862        "###;
1863        let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1864        // Parse and resolve
1865        let mut parser = crate::parser::Parser::new_with_name(
1866            main_content,
1867            main_path.to_string_lossy().to_string(),
1868        )
1869        .unwrap();
1870        let doc = parser.parse_document().unwrap();
1871        let mut resolver = test_resolver_with_builtin(builtin_path);
1872        let result = resolver.resolve(doc, main_content, main_path.clone(), None);
1873        assert!(result.is_ok(), "Resolution should succeed");
1874
1875        // Verify the imported type is in the symbol table
1876        assert!(resolver.symbol_table.types.contains_key("TestType"));
1877    }
1878    #[test]
1879    fn test_mixed_imports() {
1880        let temp_dir = TempDir::new().unwrap();
1881        let builtin_path = temp_dir.path().join("schemas");
1882        fs::create_dir_all(builtin_path.join("types")).unwrap();
1883        // Create builtin schema
1884        create_test_file(
1885            &builtin_path.join("types"),
1886            "builtin.mon",
1887            r#"{ BuiltinType: #struct { id(Number) } }"#,
1888        );
1889        // Create local schema
1890        create_test_file(
1891            temp_dir.path(),
1892            "local.mon",
1893            r#"{ LocalType: #struct { name(String) } }"#,
1894        );
1895        // Create main file with both imports
1896        let main_content = r###"
1897            import { BuiltinType } from "mon:types/builtin"
1898            import { LocalType } from "./local.mon"
1899
1900            {
1901                builtin :: BuiltinType = { id: 1 },
1902                local :: LocalType = { name: "test" }
1903            }
1904        "###;
1905        let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1906        let mut parser = crate::parser::Parser::new_with_name(
1907            main_content,
1908            main_path.to_string_lossy().to_string(),
1909        )
1910        .unwrap();
1911        let doc = parser.parse_document().unwrap();
1912        let mut resolver = test_resolver_with_builtin(builtin_path);
1913        let result = resolver.resolve(doc, main_content, main_path, None);
1914        assert!(result.is_ok());
1915        assert!(resolver.symbol_table.types.contains_key("BuiltinType"));
1916        assert!(resolver.symbol_table.types.contains_key("LocalType"));
1917    }
1918    #[test]
1919    fn test_mon_uri_not_found() {
1920        let temp_dir = TempDir::new().unwrap();
1921        let builtin_path = temp_dir.path().join("schemas");
1922        fs::create_dir_all(&builtin_path).unwrap();
1923        let main_content = r###"
1924            import { Missing } from "mon:types/nonexistent"
1925            {}
1926        "###;
1927        let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1928        let mut parser = crate::parser::Parser::new_with_name(
1929            main_content,
1930            main_path.to_string_lossy().to_string(),
1931        )
1932        .unwrap();
1933        let doc = parser.parse_document().unwrap();
1934        let mut resolver = test_resolver_with_builtin(builtin_path);
1935        let result = resolver.resolve(doc, main_content, main_path, None);
1936        assert!(matches!(result, Err(ResolverError::ModuleNotFound { .. })))
1937    }
1938    #[test]
1939    fn test_default_builtin_path() {
1940        // Test that default_builtin_path doesn't panic
1941        let path = Resolver::default_builtin_path();
1942        assert!(path.is_absolute() || path == Path::new("."));
1943    }
1944    #[test]
1945    fn test_mon_uri_auto_adds_extension() {
1946        let temp_dir = TempDir::new().unwrap();
1947        let builtin_path = temp_dir.path().join("schemas");
1948
1949        let resolver = test_resolver_with_builtin(builtin_path.clone());
1950        // Should add .mon extension
1951        let resolved = resolver.resolve_import_path("mon:config/app", Path::new("."));
1952        assert_eq!(resolved, builtin_path.join("config/app.mon"));
1953        assert!(resolved.to_string_lossy().ends_with(".mon"));
1954    }
1955    #[test]
1956    fn test_environment_variable_override() {
1957        // Set environment variable
1958        std::env::set_var("MON_BUILTIN_PATH", "/custom/builtin/path");
1959
1960        let path = Resolver::default_builtin_path();
1961
1962        // Clean up
1963        std::env::remove_var("MON_BUILTIN_PATH");
1964
1965        assert_eq!(path, PathBuf::from("/custom/builtin/path"));
1966    }
1967    #[test]
1968    fn test_circular_dependency_with_mon_imports() {
1969        let temp_dir = TempDir::new().unwrap();
1970        let builtin_path = temp_dir.path().join("schemas");
1971        fs::create_dir_all(&builtin_path).unwrap();
1972        // Create circular dependency: a.mon -> b.mon -> a.mon
1973        create_test_file(
1974            &builtin_path,
1975            "a.mon",
1976            r###"import { B } from "mon:b" { A: #struct { b(B) } }"###,
1977        );
1978        create_test_file(
1979            &builtin_path,
1980            "b.mon",
1981            r###"import { A } from "mon:a" { B: #struct { a(A) } }"###,
1982        );
1983        let main_content = r###"import { A } from "mon:a" {}"###;
1984        let main_path = create_test_file(temp_dir.path(), "main.mon", main_content);
1985        let mut parser = crate::parser::Parser::new_with_name(
1986            main_content,
1987            main_path.to_string_lossy().to_string(),
1988        )
1989        .unwrap();
1990        let doc = parser.parse_document().unwrap();
1991        let mut resolver = test_resolver_with_builtin(builtin_path);
1992        let result = resolver.resolve(doc, main_content, main_path, None);
1993        assert!(matches!(
1994            result,
1995            Err(ResolverError::CircularDependency { .. })
1996        ));
1997    }
1998}