1use shape_ast::error::{Result, ShapeError, span_to_location};
7
8pub mod symbol_table;
9pub mod types;
10pub mod validator;
11
12mod 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
31pub fn type_annotation_to_type(annotation: &TypeAnnotation) -> types::Type {
33 type_annotation_to_type_with_aliases(annotation, None)
34}
35
36pub 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 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" 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 type_annotation_to_type_with_aliases(inner, symbol_table)
102 }
103 TypeAnnotation::Object(fields) => {
104 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 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, TypeAnnotation::Intersection(types) => {
130 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, 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#[derive(Debug, Clone)]
157pub struct TypeWarning {
158 pub message: String,
160 pub span: Span,
162}
163
164pub struct SemanticAnalyzer {
166 symbol_table: SymbolTable,
168 inference_engine: TypeInferenceEngine,
170 validator: Validator,
172 pattern_library: PatternLibrary,
174 source: Option<String>,
176 warnings: Vec<TypeWarning>,
178 pub(crate) exported_symbols: std::collections::HashSet<String>,
180 pre_registered_functions: std::collections::HashSet<String>,
182}
183
184impl SemanticAnalyzer {
185 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 builtins::register_builtins(&mut analyzer.symbol_table);
200
201 analyzer.register_stdlib_module_globals();
204
205 analyzer
206 }
207
208 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 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 pub fn check_expr_type(&mut self, expr: &Expr) -> Result<types::Type> {
230 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 match self.inference_engine.infer_expr(expr) {
239 Ok(inference_type) => {
240 Ok(types::Type::from_inference_type(&inference_type))
242 }
243 Err(type_error) => {
244 Err(type_error_to_shape(
246 type_error,
247 self.source.as_deref(),
248 expr.span(),
249 ))
250 }
251 }
252 }
253
254 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 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 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 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 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 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 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 pub fn warnings(&self) -> &[TypeWarning] {
332 &self.warnings
333 }
334
335 pub fn take_warnings(&mut self) -> Vec<TypeWarning> {
337 std::mem::take(&mut self.warnings)
338 }
339
340 pub fn analyze(&mut self, program: &Program) -> Result<()> {
346 self.symbol_table.push_scope();
348
349 self.inference_engine.run_hoisting_prepass(program);
352
353 self.pre_register_functions(program);
357
358 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 pub fn analyze_incremental(&mut self, program: &Program) -> Result<()> {
380 self.inference_engine.run_hoisting_prepass(program);
382
383 for item in &program.items {
385 self.analyze_item(item)?;
386 }
387
388 Ok(())
389 }
390
391 pub fn init_repl_scope(&mut self) {
395 self.symbol_table.push_scope();
396 self.symbol_table.set_allow_redefinition(true);
397 }
398
399 fn pre_register_functions(&mut self, program: &Program) {
403 self.pre_registered_functions.clear();
404
405 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 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 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 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 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 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 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 self.analyze_statement(stmt)
522 }
523 Item::Optimize(_opt_stmt, _) => {
524 Ok(())
526 }
527 Item::AnnotationDef(_ann_def, _) => {
528 Ok(())
530 }
531 Item::StructType(struct_def, _) => {
532 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 Ok(())
542 }
543 Item::Comptime(stmts, _) => {
544 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 Ok(())
561 }
562 Item::ForeignFunction(_, _) => {
563 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 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
704fn 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 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
746fn 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;