Skip to main content

shape_runtime/semantic/
mod.rs

1//! Semantic analysis module for Shape
2//!
3//! This module performs type checking, symbol resolution, and validation
4//! of the parsed AST before execution.
5
6use shape_ast::error::{Result, ShapeError, span_to_location};
7
8pub mod symbol_table;
9pub mod types;
10pub mod validator;
11
12// Analysis modules
13mod builtins;
14mod control_flow;
15mod function_analysis;
16mod module_analysis;
17mod pattern_analysis;
18mod stream_analysis;
19mod test_analysis;
20
21use crate::extensions::ParsedModuleSchema;
22use crate::pattern_library::PatternLibrary;
23use crate::snapshot::SemanticSnapshot;
24use crate::type_system::{TypeInferenceEngine, error_bridge::type_error_to_shape};
25use shape_ast::ast::{
26    Expr, FunctionParam, Item, ObjectTypeField, Program, Span, Spanned, TypeAnnotation, TypeName,
27};
28use symbol_table::SymbolTable;
29use validator::Validator;
30
31/// Convert a TypeAnnotation from the AST to a semantic Type
32pub fn type_annotation_to_type(annotation: &TypeAnnotation) -> types::Type {
33    type_annotation_to_type_with_aliases(annotation, None)
34}
35
36/// Convert a TypeAnnotation to a semantic Type, resolving type aliases via symbol table
37pub fn type_annotation_to_type_with_aliases(
38    annotation: &TypeAnnotation,
39    symbol_table: Option<&SymbolTable>,
40) -> types::Type {
41    match annotation {
42        TypeAnnotation::Basic(name) => match name.as_str() {
43            "number" | "Number" | "float" | "int" => types::Type::Number,
44            "string" | "String" => types::Type::String,
45            "bool" | "Bool" | "boolean" => types::Type::Bool,
46            "row" | "Row" => types::Type::Object(vec![]),
47            "color" | "Color" => types::Type::Color,
48            "timestamp" | "Timestamp" => types::Type::Timestamp,
49            "timeframe" | "Timeframe" => types::Type::Timeframe,
50            "duration" | "Duration" => types::Type::Duration,
51            "pattern" | "Pattern" => types::Type::Pattern,
52            "AnyError" | "anyerror" => types::Type::Error,
53            _ => {
54                // Try to resolve as a type alias (e.g., "Candle")
55                if let Some(st) = symbol_table {
56                    if let Some(alias_entry) = st.lookup_type_alias(name) {
57                        return type_annotation_to_type_with_aliases(
58                            &alias_entry.type_annotation,
59                            symbol_table,
60                        );
61                    }
62                }
63                types::Type::Unknown
64            }
65        },
66        TypeAnnotation::Array(elem) => types::Type::Array(Box::new(
67            type_annotation_to_type_with_aliases(elem, symbol_table),
68        )),
69        TypeAnnotation::Generic { name, args } => match name.as_str() {
70            "Column" if !args.is_empty() => types::Type::Column(Box::new(
71                type_annotation_to_type_with_aliases(&args[0], symbol_table),
72            )),
73            "Vec" if !args.is_empty() => types::Type::Array(Box::new(
74                type_annotation_to_type_with_aliases(&args[0], symbol_table),
75            )),
76            "Mat" if !args.is_empty() => types::Type::Matrix(Box::new(
77                type_annotation_to_type_with_aliases(&args[0], symbol_table),
78            )),
79            "Result" if !args.is_empty() => types::Type::Result(Box::new(
80                type_annotation_to_type_with_aliases(&args[0], symbol_table),
81            )),
82            // Option<T> is represented as nullable T in the legacy semantic layer.
83            "Option" if !args.is_empty() => {
84                type_annotation_to_type_with_aliases(&args[0], symbol_table)
85            }
86            _ => types::Type::Unknown,
87        },
88        TypeAnnotation::Function { params, returns } => {
89            let param_types: Vec<types::Type> = params
90                .iter()
91                .map(|p| type_annotation_to_type_with_aliases(&p.type_annotation, symbol_table))
92                .collect();
93            let return_type = type_annotation_to_type_with_aliases(returns, symbol_table);
94            types::Type::Function {
95                params: param_types,
96                returns: Box::new(return_type),
97            }
98        }
99        TypeAnnotation::Optional(inner) => {
100            // Optional types are represented as the inner type for now
101            type_annotation_to_type_with_aliases(inner, symbol_table)
102        }
103        TypeAnnotation::Object(fields) => {
104            // Convert object type annotation to Type::Object with fields
105            let type_fields: Vec<(String, types::Type)> = fields
106                .iter()
107                .map(|f| {
108                    (
109                        f.name.clone(),
110                        type_annotation_to_type_with_aliases(&f.type_annotation, symbol_table),
111                    )
112                })
113                .collect();
114            types::Type::Object(type_fields)
115        }
116        TypeAnnotation::Reference(name) => {
117            // Try to resolve type reference via symbol table
118            if let Some(st) = symbol_table {
119                if let Some(alias_entry) = st.lookup_type_alias(name) {
120                    return type_annotation_to_type_with_aliases(
121                        &alias_entry.type_annotation,
122                        symbol_table,
123                    );
124                }
125            }
126            types::Type::Unknown
127        }
128        TypeAnnotation::Union(_) => types::Type::Unknown, // Union types not fully supported yet
129        TypeAnnotation::Intersection(types) => {
130            // Intersection types merge all fields from component object types
131            let mut all_fields = Vec::new();
132            for ty in types {
133                if let types::Type::Object(fields) =
134                    type_annotation_to_type_with_aliases(ty, symbol_table)
135                {
136                    all_fields.extend(fields);
137                }
138            }
139            if all_fields.is_empty() {
140                types::Type::Unknown
141            } else {
142                types::Type::Object(all_fields)
143            }
144        }
145        TypeAnnotation::Tuple(_) => types::Type::Unknown, // Tuple types not fully supported yet
146        TypeAnnotation::Void => types::Type::Unknown,
147        TypeAnnotation::Any => types::Type::Unknown,
148        TypeAnnotation::Never => types::Type::Error,
149        TypeAnnotation::Null => types::Type::Unknown,
150        TypeAnnotation::Undefined => types::Type::Unknown,
151        TypeAnnotation::Dyn(_) => types::Type::Unknown,
152    }
153}
154
155/// A warning generated during type checking (not an error)
156#[derive(Debug, Clone)]
157pub struct TypeWarning {
158    /// Warning message
159    pub message: String,
160    /// Location in source code
161    pub span: Span,
162}
163
164/// The main semantic analyzer
165pub struct SemanticAnalyzer {
166    /// Symbol table for tracking patterns, variables, and functions
167    symbol_table: SymbolTable,
168    /// Type inference engine (replaces legacy TypeChecker)
169    inference_engine: TypeInferenceEngine,
170    /// Validator for semantic rules
171    validator: Validator,
172    /// Built-in pattern library for validation
173    pattern_library: PatternLibrary,
174    /// Source code for error location reporting (optional for backward compatibility)
175    source: Option<String>,
176    /// Type warnings (not errors, compilation continues)
177    warnings: Vec<TypeWarning>,
178    /// Track exported symbols for module analysis
179    pub(crate) exported_symbols: std::collections::HashSet<String>,
180    /// Function names pre-registered for mutual recursion (consumed during main pass)
181    pre_registered_functions: std::collections::HashSet<String>,
182}
183
184impl SemanticAnalyzer {
185    /// Create a new semantic analyzer
186    pub fn new() -> Self {
187        let mut analyzer = Self {
188            symbol_table: SymbolTable::new(),
189            inference_engine: TypeInferenceEngine::new(),
190            validator: Validator::new(),
191            pattern_library: PatternLibrary::new(),
192            source: None,
193            warnings: Vec::new(),
194            exported_symbols: std::collections::HashSet::new(),
195            pre_registered_functions: std::collections::HashSet::new(),
196        };
197
198        // Register built-in functions
199        builtins::register_builtins(&mut analyzer.symbol_table);
200
201        // Register VM-native stdlib modules so their globals are visible
202        // during semantic analysis (regex, http, crypto, env, log).
203        analyzer.register_stdlib_module_globals();
204
205        analyzer
206    }
207
208    /// Set the source code for better error location reporting (builder pattern)
209    pub fn with_source(mut self, source: impl Into<String>) -> Self {
210        let source_str = source.into();
211        self.validator.set_source(source_str.clone());
212        self.symbol_table.set_source(source_str.clone());
213        self.source = Some(source_str);
214        self
215    }
216
217    /// Set the source code for error location reporting (mutable reference)
218    pub fn set_source(&mut self, source: impl Into<String>) {
219        let source_str = source.into();
220        self.validator.set_source(source_str.clone());
221        self.symbol_table.set_source(source_str.clone());
222        self.source = Some(source_str);
223    }
224
225    /// Check the type of an expression using the inference engine
226    ///
227    /// This is the main bridge method that replaces the legacy type checker.
228    /// It infers the type and converts the result to the legacy Type enum.
229    pub fn check_expr_type(&mut self, expr: &Expr) -> Result<types::Type> {
230        // Sync symbol table to inference engine before type checking
231        self.inference_engine
232            .env
233            .import_from_symbol_table(&self.symbol_table);
234        self.inference_engine
235            .sync_callable_defaults_from_symbol_table(&self.symbol_table);
236
237        // Infer the expression type
238        match self.inference_engine.infer_expr(expr) {
239            Ok(inference_type) => {
240                // Convert inference Type to legacy Type
241                Ok(types::Type::from_inference_type(&inference_type))
242            }
243            Err(type_error) => {
244                // Convert TypeError to ShapeError
245                Err(type_error_to_shape(
246                    type_error,
247                    self.source.as_deref(),
248                    expr.span(),
249                ))
250            }
251        }
252    }
253
254    /// Register extension module namespaces with their export type schemas.
255    /// Each module becomes a Symbol::Module with a concrete Object type annotation.
256    /// Must be called before analyze() so the type system recognizes modules.
257    pub fn register_extension_modules(&mut self, modules: &[ParsedModuleSchema]) {
258        for module in modules {
259            let export_names: Vec<String> =
260                module.functions.iter().map(|f| f.name.clone()).collect();
261            let type_ann = build_module_type(module);
262            let _ = self
263                .symbol_table
264                .define_module(&module.module_name, export_names, type_ann);
265        }
266    }
267
268    /// Register the VM-native stdlib modules as known globals.
269    ///
270    /// These modules (regex, http, crypto, env, log) have full Rust
271    /// implementations in `shape_runtime::stdlib` and are auto-registered
272    /// in `VirtualMachine::new()`. This method makes them visible to the
273    /// semantic analyzer so that `regex.is_match(...)` etc. compile.
274    fn register_stdlib_module_globals(&mut self) {
275        let modules = crate::module_exports::ModuleExports::stdlib_module_schemas();
276        self.register_extension_modules(&modules);
277    }
278
279    /// Create a semantic error with location information from a span
280    pub fn error_at(&self, span: Span, message: impl Into<String>) -> ShapeError {
281        let location = self
282            .source
283            .as_ref()
284            .map(|src| span_to_location(src, span, None));
285        ShapeError::SemanticError {
286            message: message.into(),
287            location,
288        }
289    }
290
291    /// Create a semantic error with location and a hint
292    pub fn error_at_with_hint(
293        &self,
294        span: Span,
295        message: impl Into<String>,
296        hint: impl Into<String>,
297    ) -> ShapeError {
298        let location = self
299            .source
300            .as_ref()
301            .map(|src| span_to_location(src, span, None).with_hint(hint));
302        ShapeError::SemanticError {
303            message: message.into(),
304            location,
305        }
306    }
307
308    /// Add a type warning (not an error, analysis continues)
309    pub fn add_warning(&mut self, span: Span, message: impl Into<String>) {
310        self.warnings.push(TypeWarning {
311            message: message.into(),
312            span,
313        });
314    }
315
316    /// Snapshot semantic analyzer state for resumability.
317    pub fn snapshot(&self) -> SemanticSnapshot {
318        SemanticSnapshot {
319            symbol_table: self.symbol_table.clone(),
320            exported_symbols: self.exported_symbols.clone(),
321        }
322    }
323
324    /// Restore semantic analyzer state from a snapshot.
325    pub fn restore_from_snapshot(&mut self, snapshot: SemanticSnapshot) {
326        self.symbol_table = snapshot.symbol_table;
327        self.exported_symbols = snapshot.exported_symbols;
328    }
329
330    /// Get all warnings generated during analysis
331    pub fn warnings(&self) -> &[TypeWarning] {
332        &self.warnings
333    }
334
335    /// Take ownership of warnings (clears the internal list)
336    pub fn take_warnings(&mut self) -> Vec<TypeWarning> {
337        std::mem::take(&mut self.warnings)
338    }
339
340    /// Analyze a complete program (script mode - isolated execution)
341    ///
342    /// Creates a fresh scope for this program and pops it after analysis.
343    /// Each call is isolated - no state persists between calls.
344    /// Collects all semantic errors and reports them together.
345    pub fn analyze(&mut self, program: &Program) -> Result<()> {
346        // Push a new scope for user code so it can shadow built-in functions
347        self.symbol_table.push_scope();
348
349        // Run optimistic hoisting pre-pass to collect property assignments
350        // This enables: let a = {x: 1}; a.y = 2; // y is hoisted
351        self.inference_engine.run_hoisting_prepass(program);
352
353        // Pre-register all top-level function signatures before analyzing bodies.
354        // This enables mutual recursion: `is_even` can call `is_odd` and vice
355        // versa, because both names are in scope when their bodies are checked.
356        self.pre_register_functions(program);
357
358        // Main pass: analyze all items, collecting errors
359        let mut errors = Vec::new();
360        for item in &program.items {
361            if let Err(e) = self.analyze_item(item) {
362                errors.push(e);
363            }
364        }
365
366        self.symbol_table.pop_scope();
367
368        match errors.len() {
369            0 => Ok(()),
370            1 => Err(errors.into_iter().next().unwrap()),
371            _ => Err(ShapeError::MultiError(errors)),
372        }
373    }
374
375    /// Analyze a program incrementally (REPL mode - persistent state)
376    ///
377    /// Variables and functions defined in previous calls remain visible.
378    /// Call `init_repl_scope()` once before the first `analyze_incremental()` call.
379    pub fn analyze_incremental(&mut self, program: &Program) -> Result<()> {
380        // Run optimistic hoisting pre-pass to collect property assignments
381        self.inference_engine.run_hoisting_prepass(program);
382
383        // Main pass: analyze all items
384        for item in &program.items {
385            self.analyze_item(item)?;
386        }
387
388        Ok(())
389    }
390
391    /// Initialize the REPL scope (call once before first analyze_incremental)
392    ///
393    /// Pushes a user scope that will persist across all incremental analyses.
394    pub fn init_repl_scope(&mut self) {
395        self.symbol_table.push_scope();
396        self.symbol_table.set_allow_redefinition(true);
397    }
398
399    /// Pre-register all top-level function signatures so that mutual
400    /// recursion is possible.  Only names and parameter/return types are
401    /// recorded; bodies are NOT analyzed here.
402    fn pre_register_functions(&mut self, program: &Program) {
403        self.pre_registered_functions.clear();
404
405        // Pre-register struct types so they can be referenced in any order
406        for item in &program.items {
407            let struct_def = match item {
408                Item::StructType(s, _) => s,
409                Item::Export(export, _) => {
410                    if let shape_ast::ast::ExportItem::Struct(s) = &export.item {
411                        s
412                    } else {
413                        continue;
414                    }
415                }
416                _ => continue,
417            };
418            self.inference_engine
419                .struct_type_defs
420                .insert(struct_def.name.clone(), struct_def.clone());
421        }
422
423        for item in &program.items {
424            // Extract (name, params, return_type) from regular and foreign functions
425            let (name, params, return_type_ann) = match item {
426                Item::Function(f, _) => (&f.name, &f.params, &f.return_type),
427                Item::ForeignFunction(f, _) => (&f.name, &f.params, &f.return_type),
428                Item::Export(export, _) => match &export.item {
429                    shape_ast::ast::ExportItem::Function(f) => (&f.name, &f.params, &f.return_type),
430                    shape_ast::ast::ExportItem::ForeignFunction(f) => {
431                        (&f.name, &f.params, &f.return_type)
432                    }
433                    _ => continue,
434                },
435                _ => continue,
436            };
437
438            // Skip duplicates — only pre-register the first occurrence.
439            // The main pass will detect and report duplicates.
440            if self.pre_registered_functions.contains(name) {
441                continue;
442            }
443
444            let param_types: Vec<types::Type> = params
445                .iter()
446                .map(|p| {
447                    p.type_annotation
448                        .as_ref()
449                        .map(|ann| {
450                            type_annotation_to_type_with_aliases(ann, Some(&self.symbol_table))
451                        })
452                        .unwrap_or(types::Type::Unknown)
453                })
454                .collect();
455
456            let return_type = return_type_ann
457                .as_ref()
458                .map(|ann| type_annotation_to_type_with_aliases(ann, Some(&self.symbol_table)))
459                .unwrap_or(types::Type::Unknown);
460
461            let defaults: Vec<bool> = params.iter().map(|p| p.default_value.is_some()).collect();
462
463            // Ignore errors — the main pass will report them properly.
464            let _ = self.symbol_table.define_function_with_defaults(
465                name,
466                param_types,
467                return_type,
468                defaults,
469            );
470            self.pre_registered_functions.insert(name.clone());
471        }
472    }
473
474    /// Analyze a single item
475    fn analyze_item(&mut self, item: &Item) -> Result<()> {
476        match item {
477            Item::Query(query, span) => self.analyze_query(query, *span),
478            Item::VariableDecl(var_decl, _) => self.analyze_variable_decl(var_decl),
479            Item::Assignment(assignment, _) => self.analyze_assignment(assignment),
480            Item::Expression(expr, _) => {
481                self.check_expr_type(expr)?;
482                Ok(())
483            }
484            Item::Import(import, _) => self.analyze_import(import),
485            Item::Export(export, _) => self.analyze_export(export),
486            Item::Module(module_def, _) => {
487                // Register the module name as a variable in the current scope
488                // so that `module_name.member` access works
489                self.symbol_table.define_variable(
490                    &module_def.name,
491                    types::Type::Unknown,
492                    shape_ast::ast::VarKind::Const,
493                    true,
494                )?;
495                self.symbol_table.push_scope();
496                let result = (|| {
497                    for inner in &module_def.items {
498                        self.analyze_item(inner)?;
499                    }
500                    Ok(())
501                })();
502                self.symbol_table.pop_scope();
503                result
504            }
505            Item::Function(function, _) => self.analyze_function(function),
506            Item::Test(test, _) => self.analyze_test(test),
507            Item::TypeAlias(alias, _) => self.analyze_type_alias(alias),
508            Item::Interface(interface, _) => self.analyze_interface(interface),
509            Item::Trait(trait_def, _) => {
510                // Keep semantic inference env in sync so expression checks can
511                // resolve trait requirements in the same file.
512                self.inference_engine.env.define_trait(trait_def);
513                Ok(())
514            }
515            Item::Enum(enum_def, _) => self.analyze_enum(enum_def),
516            Item::Extend(extend_stmt, _) => self.analyze_extend(extend_stmt),
517            Item::Impl(impl_block, span) => self.register_trait_impl_metadata(impl_block, *span),
518            Item::Stream(stream_def, _) => self.analyze_stream(stream_def),
519            Item::Statement(stmt, _) => {
520                // Statements at top level are analyzed (e.g., variable declarations)
521                self.analyze_statement(stmt)
522            }
523            Item::Optimize(_opt_stmt, _) => {
524                // Optimize statements are validated during AI execution
525                Ok(())
526            }
527            Item::AnnotationDef(_ann_def, _) => {
528                // Annotation definitions are registered and processed separately
529                Ok(())
530            }
531            Item::StructType(struct_def, _) => {
532                // Register struct type in the inference engine so that
533                // expressions like `Currency.symbol` can resolve the type name.
534                self.inference_engine
535                    .struct_type_defs
536                    .insert(struct_def.name.clone(), struct_def.clone());
537                Ok(())
538            }
539            Item::DataSource(_, _) | Item::QueryDecl(_, _) => {
540                // Data source and query declarations are registered at compile time
541                Ok(())
542            }
543            Item::Comptime(stmts, _) => {
544                // Comptime blocks are evaluated at compile time with dedicated builtins.
545                // Register those names in a temporary scope so semantic analysis matches
546                // runtime compilation behavior without polluting normal scopes.
547                self.symbol_table.push_scope();
548                let result = (|| {
549                    self.register_comptime_semantic_builtins()?;
550                    for stmt in stmts {
551                        self.analyze_statement(stmt)?;
552                    }
553                    Ok(())
554                })();
555                self.symbol_table.pop_scope();
556                result
557            }
558            Item::BuiltinTypeDecl(_, _) | Item::BuiltinFunctionDecl(_, _) => {
559                // Declaration-only intrinsics carry metadata only.
560                Ok(())
561            }
562            Item::ForeignFunction(_, _) => {
563                // Foreign function bodies are opaque to Shape semantic analysis.
564                Ok(())
565            }
566        }
567    }
568
569    fn type_name_str(name: &TypeName) -> String {
570        match name {
571            TypeName::Simple(n) => n.clone(),
572            TypeName::Generic { name, .. } => name.clone(),
573        }
574    }
575
576    fn canonical_conversion_name(name: &str) -> String {
577        match name {
578            "boolean" | "Boolean" | "Bool" => "bool".to_string(),
579            "String" => "string".to_string(),
580            "Number" => "number".to_string(),
581            "Int" => "int".to_string(),
582            "Decimal" => "decimal".to_string(),
583            _ => name.to_string(),
584        }
585    }
586
587    fn conversion_name_from_annotation(annotation: &TypeAnnotation) -> Option<String> {
588        match annotation {
589            TypeAnnotation::Basic(name)
590            | TypeAnnotation::Reference(name)
591            | TypeAnnotation::Generic { name, .. } => Some(Self::canonical_conversion_name(name)),
592            _ => None,
593        }
594    }
595
596    fn register_trait_impl_metadata(
597        &mut self,
598        impl_block: &shape_ast::ast::ImplBlock,
599        span: Span,
600    ) -> Result<()> {
601        match &impl_block.trait_name {
602            TypeName::Generic { name, type_args } if name == "TryInto" || name == "Into" => {
603                if type_args.len() != 1 {
604                    return Err(self.error_at(
605                        span,
606                        format!(
607                            "{} impl must declare exactly one target: `impl {}<Target> for Source as target`",
608                            name, name
609                        ),
610                    ));
611                }
612                let target =
613                    Self::conversion_name_from_annotation(&type_args[0]).ok_or_else(|| {
614                        self.error_at(
615                            span,
616                            format!("{} target must be a concrete named type", name),
617                        )
618                    })?;
619                let selector = impl_block.impl_name.as_deref().ok_or_else(|| {
620                    self.error_at(
621                        span,
622                        format!("{} impl must declare named selector with `as target`", name),
623                    )
624                })?;
625                let selector = Self::canonical_conversion_name(selector);
626                if selector != target {
627                    return Err(self.error_at(
628                        span,
629                        format!(
630                            "{} target `{}` must match impl selector `{}`",
631                            name, target, selector
632                        ),
633                    ));
634                }
635            }
636            TypeName::Simple(name) if name == "TryInto" || name == "Into" => {
637                return Err(self.error_at(
638                    span,
639                    format!(
640                        "{} impl must use generic target form: `impl {}<Target> for Source as target`",
641                        name, name
642                    ),
643                ));
644            }
645            _ => {}
646        }
647
648        let trait_name = Self::type_name_str(&impl_block.trait_name);
649        let target_type = Self::type_name_str(&impl_block.target_type);
650        let method_names = impl_block.methods.iter().map(|m| m.name.clone()).collect();
651        let associated_types = impl_block
652            .associated_type_bindings
653            .iter()
654            .map(|binding| (binding.name.clone(), binding.concrete_type.clone()))
655            .collect();
656
657        self.inference_engine
658            .env
659            .register_trait_impl_with_assoc_types_named(
660                &trait_name,
661                &target_type,
662                impl_block.impl_name.as_deref(),
663                method_names,
664                associated_types,
665            )
666            .map_err(|msg| self.error_at(span, msg))
667    }
668
669    /// Look up a type alias (for runtime type resolution)
670    pub fn lookup_type_alias(
671        &self,
672        name: &str,
673    ) -> Option<&crate::type_system::environment::TypeAliasEntry> {
674        self.symbol_table.lookup_type_alias(name)
675    }
676
677    fn register_comptime_semantic_builtins(&mut self) -> Result<()> {
678        use crate::semantic::types::Type;
679
680        self.symbol_table.define_function(
681            "build_config",
682            vec![],
683            Type::Object(vec![
684                ("debug".to_string(), Type::Unknown),
685                ("target_arch".to_string(), Type::Unknown),
686                ("target_os".to_string(), Type::Unknown),
687                ("version".to_string(), Type::Unknown),
688            ]),
689        )?;
690        self.symbol_table.define_function(
691            "implements",
692            vec![Type::Unknown, Type::Unknown],
693            Type::Bool,
694        )?;
695        self.symbol_table
696            .define_function("warning", vec![Type::Unknown], Type::Unknown)?;
697        self.symbol_table
698            .define_function("error", vec![Type::Unknown], Type::Unknown)?;
699
700        Ok(())
701    }
702}
703
704/// Build a module's object type from its function schemas.
705/// Each export becomes an ObjectTypeField with a Function type annotation.
706fn build_module_type(schema: &ParsedModuleSchema) -> TypeAnnotation {
707    let fields: Vec<ObjectTypeField> = schema
708        .functions
709        .iter()
710        .map(|function| {
711            let params: Vec<FunctionParam> = function
712                .params
713                .iter()
714                .enumerate()
715                .map(|(idx, p)| FunctionParam {
716                    name: Some(format!("arg{}", idx)),
717                    // Module function params are marked optional since
718                    // ParsedModuleSchema doesn't carry required/optional info.
719                    // Runtime arg validation handles arity checks.
720                    optional: true,
721                    type_annotation: schema_type_to_annotation(p),
722                })
723                .collect();
724
725            let returns = function
726                .return_type
727                .as_ref()
728                .map(|r| schema_type_to_annotation(r))
729                .unwrap_or(TypeAnnotation::Any);
730
731            ObjectTypeField {
732                name: function.name.clone(),
733                optional: false,
734                type_annotation: TypeAnnotation::Function {
735                    params,
736                    returns: Box::new(returns),
737                },
738                annotations: vec![],
739            }
740        })
741        .collect();
742
743    TypeAnnotation::Object(fields)
744}
745
746/// Convert a schema type name string to a TypeAnnotation.
747fn schema_type_to_annotation(type_name: &str) -> TypeAnnotation {
748    TypeAnnotation::Basic(type_name.to_string())
749}
750
751impl Default for SemanticAnalyzer {
752    fn default() -> Self {
753        Self::new()
754    }
755}
756
757#[cfg(test)]
758mod tests;