1use std::collections::{HashMap, HashSet};
11
12use pine_ast::{Argument, ExportItem, Expr, FunctionParam, Literal, Loc, Program, Stmt};
13use pine_core::{LibraryLoader, PineOutput};
14use pine_interpreter::{BuiltinSignature, Value};
15use pine_parser::Parser;
16
17use crate::scope::{is_global_only, Namespace, SymbolKind};
18use crate::symbols::{FileId, ScopeId, ScopeKind, Symbol, SymbolId, SymbolTable};
19use pine_diagnostics::Diagnostic;
20
21pub struct Analyzer<'a, O: PineOutput> {
22 diagnostics: Vec<Diagnostic>,
23 loop_depth: u32,
25 builtins: &'a HashMap<String, Value<O>>,
27 declarations: u32,
29 library_declared: bool,
31 functions: HashMap<String, (usize, usize)>,
33 user_types: HashSet<String>,
35 fn_stack: Vec<String>,
37 call_edges: Vec<CallEdge>,
39 symbols: SymbolTable,
41 scope_ids: Vec<ScopeId>,
42 loader: Option<&'a dyn LibraryLoader>,
44}
45
46struct FileState {
48 scope_ids: Vec<ScopeId>,
49 loop_depth: u32,
50 functions: HashMap<String, (usize, usize)>,
51 user_types: HashSet<String>,
52 declarations: u32,
53 library_declared: bool,
54 fn_stack: Vec<String>,
55 call_edges: Vec<CallEdge>,
56}
57
58type CallEdge = (String, String, Loc);
60
61const SCRIPT_DECLARATIONS: &[&str] = &["study", "indicator", "strategy", "library"];
63
64const BUILTIN_TYPES: &[&str] = &[
66 "int",
67 "float",
68 "bool",
69 "string",
70 "color",
71 "line",
72 "linefill",
73 "label",
74 "box",
75 "table",
76 "polyline",
77 "array",
78 "matrix",
79 "map",
80 "footprint",
81 "volume_row",
82];
83
84fn callee_name(callee: &Expr) -> String {
86 match callee {
87 Expr::Variable { name, .. } => name.clone(),
88 Expr::MemberAccess { object, member, .. } => match object.as_ref() {
89 Expr::Variable {
90 name: namespace, ..
91 } => format!("{namespace}.{member}"),
92 _ => member.clone(),
93 },
94 _ => String::new(),
95 }
96}
97
98fn reaches(start: &str, target: &str, adjacency: &HashMap<&str, Vec<&str>>) -> bool {
100 if start == target {
101 return true;
102 }
103 let mut stack = vec![start];
104 let mut seen = HashSet::new();
105 while let Some(node) = stack.pop() {
106 if !seen.insert(node) {
107 continue;
108 }
109 if let Some(callees) = adjacency.get(node) {
110 for &callee in callees {
111 if callee == target {
112 return true;
113 }
114 stack.push(callee);
115 }
116 }
117 }
118 false
119}
120
121fn type_names(annotation: &str) -> impl Iterator<Item = &str> {
124 annotation
125 .split(['<', '>', ',', '[', ']', ' '])
126 .filter(|name| !name.is_empty())
127}
128
129fn describe_literal(literal: &Literal) -> &'static str {
131 match literal {
132 Literal::Int(_) | Literal::Number(_) => "a number",
133 Literal::String(_) => "a string",
134 Literal::Bool(_) => "a bool",
135 Literal::HexColor(_) => "a color",
136 Literal::Na => "na",
137 }
138}
139
140impl<'a, O: PineOutput> Analyzer<'a, O> {
141 pub fn new(
142 builtins: &'a HashMap<String, Value<O>>,
143 loader: Option<&'a dyn LibraryLoader>,
144 ) -> Self {
145 Self {
146 diagnostics: Vec::new(),
147 loop_depth: 0,
148 builtins,
149 declarations: 0,
150 library_declared: false,
151 functions: HashMap::new(),
152 user_types: HashSet::new(),
153 fn_stack: Vec::new(),
154 call_edges: Vec::new(),
155 symbols: SymbolTable::new(),
156 scope_ids: vec![SymbolTable::GLOBAL],
157 loader,
158 }
159 }
160
161 fn current_scope(&self) -> ScopeId {
164 *self.scope_ids.last().expect("scope stack is never empty")
165 }
166
167 fn current_file(&self) -> FileId {
168 self.symbols.scope_file(self.current_scope())
169 }
170
171 fn current_lib(&self) -> Option<String> {
172 let file = self.current_file();
173 (file != SymbolTable::MAIN).then(|| self.symbols.file_path(file).to_string())
174 }
175
176 fn enter_scope(&mut self, kind: ScopeKind) {
178 let child = self.symbols.open_scope(self.current_scope(), kind);
179 self.scope_ids.push(child);
180 }
181
182 fn exit_scope(&mut self) {
184 self.scope_ids.pop();
185 }
186
187 fn record(&mut self, mut symbol: Symbol) -> SymbolId {
189 symbol.file = self.current_file();
190 self.symbols.declare(symbol)
191 }
192
193 fn resolve(&self, name: &str) -> Option<SymbolKind> {
195 self.symbols
196 .resolve(self.current_scope(), name)
197 .map(|symbol| symbol.kind)
198 }
199
200 fn record_use(&mut self, name: &str, loc: Loc) {
202 let scope = self.current_scope();
203 if let Some(id) = self.symbols.resolve_id(scope, name) {
204 let file = self.current_file();
205 self.symbols.record_use(file, loc.position(), id);
206 }
207 }
208
209 fn infer_var_type(
211 &self,
212 type_annotation: Option<&String>,
213 initializer: Option<&Expr>,
214 ) -> Option<SymbolId> {
215 if let Some(annotation) = type_annotation {
216 let base = annotation.trim_end_matches("[]");
217 if let Some(id) = self.symbols.resolve_id(self.current_scope(), base) {
218 if matches!(
219 self.symbols.symbol(id).kind,
220 SymbolKind::Type | SymbolKind::Enum
221 ) {
222 return Some(id);
223 }
224 }
225 }
226 if let Some(Expr::Call { callee, .. }) = initializer {
228 if let Expr::MemberAccess { object, member, .. } = callee.as_ref() {
229 if member == "new" {
230 if let Some(id) = self.expr_type(object) {
231 if self.symbols.symbol(id).kind == SymbolKind::Type {
232 return Some(id);
233 }
234 }
235 }
236 }
237 }
238 None
239 }
240
241 fn owner_type(&self, id: SymbolId) -> Option<SymbolId> {
243 let symbol = self.symbols.symbol(id);
244 match symbol.kind {
245 SymbolKind::Type | SymbolKind::Enum => Some(id),
246 SymbolKind::Var => symbol.type_ref,
247 _ => None,
248 }
249 }
250
251 fn expr_type(&self, expr: &Expr) -> Option<SymbolId> {
254 let id = match expr {
255 Expr::Variable { name, .. } => self.symbols.resolve_id(self.current_scope(), name)?,
256 Expr::MemberAccess { object, member, .. } => self.resolve_member(object, member)?,
257 _ => return None,
258 };
259 self.owner_type(id)
260 }
261
262 fn resolve_member(&self, object: &Expr, member: &str) -> Option<SymbolId> {
265 if let Some(module) = self.alias_module(object) {
266 return self.symbols.exported_id(module, member);
267 }
268 let owner = self.expr_type(object)?;
269 self.symbols.member_id(owner, member)
270 }
271
272 fn unknown_member(&self, object: &Expr, member: &str) -> bool {
277 if let Some(module) = self.alias_module(object) {
279 return self.symbols.exported_id(module, member).is_none();
280 }
281 if let Expr::Variable { name, .. } = object {
285 if self.resolve(name).is_none() {
286 if let Some(Value::Object { fields, .. }) = self.builtins.get(name) {
287 return !fields.borrow().contains_key(member);
288 }
289 }
290 }
291 false
292 }
293
294 fn alias_module(&self, object: &Expr) -> Option<ScopeId> {
296 let Expr::Variable { name, .. } = object else {
297 return None;
298 };
299 let id = self.symbols.resolve_id(self.current_scope(), name)?;
300 let symbol = self.symbols.symbol(id);
301 (symbol.kind == SymbolKind::Import)
302 .then_some(symbol.module)
303 .flatten()
304 }
305
306 fn is_builtin(&self, name: &str) -> bool {
307 self.builtins.contains_key(name)
308 }
309
310 fn builtin_signature(&self, callee: &Expr) -> Option<&'static BuiltinSignature> {
313 let value = match callee {
314 Expr::Variable { name, .. } => {
315 if self.resolve(name).is_some() {
316 return None;
317 }
318 self.builtins.get(name)?.clone()
319 }
320 Expr::MemberAccess { object, member, .. } => {
321 let Expr::Variable {
322 name: namespace, ..
323 } = object.as_ref()
324 else {
325 return None;
326 };
327 if self.resolve(namespace).is_some() {
328 return None;
329 }
330 match self.builtins.get(namespace)? {
331 Value::Object { fields, .. } => fields.borrow().get(member)?.clone(),
332 _ => return None,
333 }
334 }
335 _ => return None,
336 };
337
338 match value {
339 Value::BuiltinFunction(builtin) if !builtin.signature.params.is_empty() => {
340 Some(builtin.signature)
341 }
342 Value::Object {
344 call: Some(builtin),
345 ..
346 } if !builtin.signature.params.is_empty() => Some(builtin.signature),
347 _ => None,
348 }
349 }
350
351 fn check_builtin_args(
354 &mut self,
355 name: &str,
356 signature: &BuiltinSignature,
357 args: &[Argument],
358 loc: Loc,
359 ) {
360 let positional = args
361 .iter()
362 .filter(|arg| matches!(arg, Argument::Positional(_)))
363 .count();
364
365 if let Some(max) = signature.max_positional() {
366 if positional > max {
367 self.emit(
368 "too-many-arguments",
369 loc,
370 format!("`{name}` takes at most {max} arguments, found {positional}"),
371 );
372 }
373 }
374
375 let mut index = 0;
376 for arg in args {
377 let (param, value) = match arg {
378 Argument::Positional(value) => {
379 let param = signature.positional(index);
380 index += 1;
381 (param, value)
382 }
383 Argument::Named { name: label, value } => match signature.named(label) {
384 Some(param) => (Some(param), value),
385 None => {
386 self.emit(
387 "unknown-argument",
388 loc,
389 format!("`{name}` has no argument named `{label}`"),
390 );
391 continue;
392 }
393 },
394 };
395
396 let (Some(param), Expr::Literal(literal)) = (param, value) else {
399 continue;
400 };
401 if !param.ty.accepts(literal) {
402 let found = describe_literal(literal);
403 let expected = param.ty.describe();
404 let label = param.name.clone();
405 self.emit(
406 "argument-type",
407 loc,
408 format!("`{name}` expects {expected} for `{label}`, found {found}"),
409 );
410 }
411 }
412
413 let required = signature
416 .params
417 .iter()
418 .filter(|param| param.required)
419 .count();
420 if args.len() < required {
421 self.emit(
422 "too-few-arguments",
423 loc,
424 format!(
425 "`{name}` requires at least {required} arguments, found {}",
426 args.len()
427 ),
428 );
429 }
430 }
431
432 fn run_file(&mut self, program: &Program) {
434 for stmt in &program.statements {
436 match stmt {
437 Stmt::TypeDecl { name, .. } | Stmt::EnumDecl { name, .. } => {
438 self.user_types.insert(name.clone());
439 }
440 _ => {}
441 }
442 }
443 for stmt in &program.statements {
444 self.check_stmt(stmt);
445 }
446 self.detect_recursion();
447 }
448
449 fn enter_file(&mut self, root: ScopeId) -> FileState {
451 FileState {
452 scope_ids: std::mem::replace(&mut self.scope_ids, vec![root]),
453 loop_depth: std::mem::take(&mut self.loop_depth),
454 functions: std::mem::take(&mut self.functions),
455 user_types: std::mem::take(&mut self.user_types),
456 declarations: std::mem::take(&mut self.declarations),
457 library_declared: std::mem::take(&mut self.library_declared),
458 fn_stack: std::mem::take(&mut self.fn_stack),
459 call_edges: std::mem::take(&mut self.call_edges),
460 }
461 }
462
463 fn exit_file(&mut self, saved: FileState) {
464 self.scope_ids = saved.scope_ids;
465 self.loop_depth = saved.loop_depth;
466 self.functions = saved.functions;
467 self.user_types = saved.user_types;
468 self.declarations = saved.declarations;
469 self.library_declared = saved.library_declared;
470 self.fn_stack = saved.fn_stack;
471 self.call_edges = saved.call_edges;
472 }
473
474 fn resolve_import(&mut self, path: &str, loc: Loc) -> Option<(FileId, ScopeId)> {
477 if let Some(file) = self.symbols.file_by_path(path) {
478 return Some((file, self.symbols.file_root(file)));
479 }
480 let loader = self.loader?;
481 let source = match loader.load_library(path) {
482 Ok(source) => source,
483 Err(err) => {
484 self.emit(
485 "import-error",
486 loc,
487 format!("cannot load library `{path}`: {err}"),
488 );
489 return None;
490 }
491 };
492 let program = match Parser::parse_source(&source) {
493 Ok(program) => program,
494 Err(err) => {
495 self.emit(
496 "import-parse-error",
497 loc,
498 format!("cannot parse library `{path}`: {err}"),
499 );
500 return None;
501 }
502 };
503 let (file, root) = self.symbols.add_file(path);
504 let saved = self.enter_file(root);
505 self.run_file(&program);
506 let is_library = self.library_declared;
507 self.exit_file(saved);
508 if !is_library {
510 self.emit(
511 "not-a-library",
512 loc,
513 format!("imported script `{path}` has no `library()` declaration"),
514 );
515 }
516 Some((file, root))
517 }
518
519 pub fn analyze(mut self, program: &Program) -> Vec<Diagnostic> {
521 self.run_file(program);
522 self.diagnostics
523 }
524
525 pub fn into_analysis(mut self, program: &Program) -> (Vec<Diagnostic>, SymbolTable) {
528 self.run_file(program);
529 (self.diagnostics, self.symbols)
530 }
531
532 fn detect_recursion(&mut self) {
534 let cycles: Vec<CallEdge> = {
535 let mut adjacency: HashMap<&str, Vec<&str>> = HashMap::new();
536 for (caller, callee, _) in &self.call_edges {
537 adjacency.entry(caller).or_default().push(callee);
538 }
539 self.call_edges
540 .iter()
541 .filter(|(caller, callee, _)| reaches(callee, caller, &adjacency))
542 .cloned()
543 .collect()
544 };
545 for (caller, callee, pos) in cycles {
546 let message = if caller == callee {
547 format!("`{caller}` calls itself; Pine does not allow recursion")
548 } else {
549 format!("`{caller}` and `{callee}` call each other; Pine does not allow recursion")
550 };
551 self.emit("recursion", pos, message);
552 }
553 }
554
555 fn emit(&mut self, rule: &'static str, loc: Loc, message: impl Into<String>) {
556 self.diagnostics
557 .push(Diagnostic::error(rule, loc.position(), message).in_file(self.current_lib()));
558 }
559
560 fn warn(&mut self, rule: &'static str, loc: Loc, message: impl Into<String>) {
561 self.diagnostics
562 .push(Diagnostic::warning(rule, loc.position(), message).in_file(self.current_lib()));
563 }
564
565 fn check_shadow(&mut self, name: &str, loc: Loc) {
567 if self.is_builtin(name) {
568 self.warn(
569 "shadows-builtin",
570 loc,
571 format!("declaration of `{name}` shadows a built-in"),
572 );
573 }
574 }
575
576 fn check_type_annotation(&mut self, annotation: Option<&String>, loc: Loc) {
579 let Some(annotation) = annotation else {
580 return;
581 };
582 for name in type_names(annotation) {
583 if !BUILTIN_TYPES.contains(&name) && !self.user_types.contains(name) {
584 self.emit("unknown-type", loc, format!("unknown type `{name}`"));
585 return;
586 }
587 }
588 }
589
590 fn check_call_arity(
592 &mut self,
593 name: &str,
594 supplied: usize,
595 required: usize,
596 total: usize,
597 loc: Loc,
598 ) {
599 if supplied < required {
600 self.emit(
601 "too-few-arguments",
602 loc,
603 format!("`{name}` requires at least {required} arguments, found {supplied}"),
604 );
605 } else if supplied > total {
606 self.emit(
607 "too-many-arguments",
608 loc,
609 format!("`{name}` takes at most {total} arguments, found {supplied}"),
610 );
611 }
612 }
613
614 fn analyze_function(
617 &mut self,
618 name: &str,
619 loc: Loc,
620 params: &[FunctionParam],
621 body: &[Stmt],
622 ) -> SymbolId {
623 let scope = self.current_scope();
624 if self
625 .symbols
626 .declared_locally_in(scope, name, Namespace::Value)
627 {
628 self.emit(
629 "duplicate-declaration",
630 loc,
631 format!("`{name}` is already declared in this scope"),
632 );
633 }
634 let id = self.record(
635 Symbol::new(name, SymbolKind::Function, loc.position(), scope)
636 .with_params(params.iter().map(|p| p.name.clone()).collect()),
637 );
638 let required = params.iter().filter(|p| p.default_value.is_none()).count();
640 self.functions
641 .insert(name.to_string(), (required, params.len()));
642 for param in params {
643 self.check_type_annotation(param.type_annotation.as_ref(), param.loc);
644 }
645 self.fn_stack.push(name.to_string());
646 self.function_body(
647 params.iter().map(|p| {
648 (
649 p.name.as_str(),
650 p.default_value.as_ref(),
651 p.loc,
652 p.type_annotation.as_ref(),
653 )
654 }),
655 body,
656 );
657 self.fn_stack.pop();
658 id
659 }
660
661 fn declare(&mut self, name: &str, kind: SymbolKind, loc: Loc) -> SymbolId {
663 let scope = self.current_scope();
664 if self
665 .symbols
666 .declared_locally_in(scope, name, kind.namespace())
667 {
668 self.emit(
669 "duplicate-declaration",
670 loc,
671 format!("`{name}` is already declared in this scope"),
672 );
673 }
674 self.record(Symbol::new(name, kind, loc.position(), scope))
675 }
676
677 fn block(&mut self, body: &[Stmt]) {
679 self.enter_scope(ScopeKind::Block);
680 for stmt in body {
681 self.check_stmt(stmt);
682 }
683 self.exit_scope();
684 }
685
686 fn loop_body(&mut self, body: &[Stmt]) {
688 self.loop_depth += 1;
689 for stmt in body {
690 self.check_stmt(stmt);
691 }
692 self.loop_depth -= 1;
693 }
694
695 fn function_body<'p>(
697 &mut self,
698 params: impl Iterator<Item = (&'p str, Option<&'p Expr>, Loc, Option<&'p String>)>,
699 body: &[Stmt],
700 ) {
701 self.enter_scope(ScopeKind::Function);
702 let saved_loop_depth = self.loop_depth;
703 self.loop_depth = 0;
704 let scope = self.current_scope();
705 for (name, default, loc, type_annotation) in params {
706 if let Some(default) = default {
707 self.check_expr(default);
708 }
709 self.check_shadow(name, loc);
710 if self.symbols.declared_locally(scope, name) {
711 self.emit(
712 "duplicate-parameter",
713 loc,
714 format!("parameter `{name}` is declared more than once"),
715 );
716 }
717 self.record(
718 Symbol::new(name, SymbolKind::Var, loc.position(), scope)
719 .with_type(type_annotation.cloned()),
720 );
721 }
722 for stmt in body {
723 self.check_stmt(stmt);
724 }
725 self.loop_depth = saved_loop_depth;
726 self.exit_scope();
727 }
728
729 fn check_stmt(&mut self, stmt: &Stmt) {
730 match stmt {
731 Stmt::VarDecl {
732 name,
733 initializer,
734 type_annotation,
735 loc,
736 ..
737 } => {
738 self.check_type_annotation(type_annotation.as_ref(), *loc);
739 self.check_shadow(name, *loc);
740 if let Some(Expr::Function { params, body }) = initializer {
741 self.analyze_function(name, *loc, params, body);
743 } else {
744 if let Some(init) = initializer {
747 self.check_expr(init);
748 }
749 let scope = self.current_scope();
750 if self
751 .symbols
752 .declared_locally_in(scope, name, Namespace::Value)
753 {
754 self.emit(
755 "duplicate-declaration",
756 *loc,
757 format!(
758 "`{name}` is already declared in this scope (use `:=` to reassign)"
759 ),
760 );
761 }
762 let type_ref =
763 self.infer_var_type(type_annotation.as_ref(), initializer.as_ref());
764 self.record(
765 Symbol::new(name, SymbolKind::Var, loc.position(), scope)
766 .with_type(type_annotation.clone())
767 .with_type_ref(type_ref),
768 );
769 }
770 }
771 Stmt::Assignment { target, value } => {
772 self.check_expr(value);
773 self.check_assign_target(target);
774 }
775 Stmt::TupleAssignment {
776 names, value, loc, ..
777 } => {
778 self.check_expr(value);
779 let scope = self.current_scope();
780 for name in names {
781 if name == "_" {
784 continue;
785 }
786 self.check_shadow(name, *loc);
787 if self
788 .symbols
789 .declared_locally_in(scope, name, Namespace::Value)
790 {
791 self.emit(
792 "duplicate-declaration",
793 *loc,
794 format!("`{name}` is already declared in this scope"),
795 );
796 }
797 self.record(Symbol::new(name, SymbolKind::Var, loc.position(), scope));
798 }
799 }
800 Stmt::Expression(expr) => self.check_expr(expr),
801 Stmt::If {
802 condition,
803 then_branch,
804 else_if_branches,
805 else_branch,
806 } => {
807 self.check_expr(condition);
808 self.block(then_branch);
809 for (cond, body) in else_if_branches {
810 self.check_expr(cond);
811 self.block(body);
812 }
813 if let Some(body) = else_branch {
814 self.block(body);
815 }
816 }
817 Stmt::For {
818 var_name,
819 from,
820 to,
821 step,
822 body,
823 loc,
824 } => {
825 self.check_expr(from);
826 self.check_expr(to);
827 if let Some(step) = step {
828 self.check_expr(step);
829 }
830 self.enter_scope(ScopeKind::Block);
831 self.check_shadow(var_name, *loc);
832 let scope = self.current_scope();
833 self.record(Symbol::new(
834 var_name,
835 SymbolKind::Var,
836 loc.position(),
837 scope,
838 ));
839 self.loop_body(body);
840 self.exit_scope();
841 }
842 Stmt::ForIn {
843 index_var,
844 item_var,
845 collection,
846 body,
847 loc,
848 } => {
849 self.check_expr(collection);
850 self.enter_scope(ScopeKind::Block);
851 let scope = self.current_scope();
852 if let Some(idx) = index_var {
853 self.check_shadow(idx, *loc);
854 self.record(Symbol::new(idx, SymbolKind::Var, loc.position(), scope));
855 }
856 self.check_shadow(item_var, *loc);
857 self.record(Symbol::new(
858 item_var,
859 SymbolKind::Var,
860 loc.position(),
861 scope,
862 ));
863 self.loop_body(body);
864 self.exit_scope();
865 }
866 Stmt::While { condition, body } => {
867 self.check_expr(condition);
868 self.enter_scope(ScopeKind::Block);
869 self.loop_body(body);
870 self.exit_scope();
871 }
872 Stmt::Break { loc } => self.check_loop_keyword("break", *loc),
873 Stmt::Continue { loc } => self.check_loop_keyword("continue", *loc),
874 Stmt::FunctionDecl {
875 name,
876 params,
877 body,
878 export,
879 loc,
880 } => {
881 self.check_shadow(name, *loc);
882 let id = self.analyze_function(name, *loc, params, body);
883 if *export {
884 self.symbols.mark_exported(id);
885 }
886 }
887 Stmt::MethodDecl {
888 name,
889 params,
890 body,
891 export,
892 loc,
893 } => {
894 let scope = self.current_scope();
896 let id = self.record(
897 Symbol::new(name, SymbolKind::Function, loc.position(), scope)
898 .with_params(params.iter().map(|p| p.name.clone()).collect()),
899 );
900 if *export {
901 self.symbols.mark_exported(id);
902 }
903 for param in params {
904 self.check_type_annotation(param.type_annotation.as_ref(), param.loc);
905 }
906 self.function_body(
907 params.iter().map(|p| {
908 (
909 p.name.as_str(),
910 p.default_value.as_ref(),
911 p.loc,
912 p.type_annotation.as_ref(),
913 )
914 }),
915 body,
916 );
917 }
918 Stmt::TypeDecl {
919 name,
920 fields,
921 export,
922 loc,
923 } => {
924 let owner = self.declare(name, SymbolKind::Type, *loc);
925 if *export {
926 self.symbols.mark_exported(owner);
927 }
928 for field in fields {
929 self.check_type_annotation(Some(&field.type_annotation), field.loc);
930 self.symbols.declare_member(
931 owner,
932 &field.name,
933 field.loc.position(),
934 Some(field.type_annotation.clone()),
935 );
936 }
937 }
938 Stmt::EnumDecl {
939 name,
940 fields,
941 export,
942 loc,
943 } => {
944 let owner = self.declare(name, SymbolKind::Enum, *loc);
945 if *export {
946 self.symbols.mark_exported(owner);
947 }
948 for field in fields {
949 self.symbols
950 .declare_member(owner, &field.name, field.loc.position(), None);
951 }
952 }
953 Stmt::Import { path, alias, loc } => {
954 let id = self.declare(alias, SymbolKind::Import, *loc);
955 if let Some((_, root)) = self.resolve_import(path, *loc) {
956 self.symbols.set_module(id, root);
957 }
958 }
959 Stmt::Export { item } => {
961 let name = match item {
962 ExportItem::Function(name) | ExportItem::Type(name) => name,
963 };
964 if let Some(id) = self.symbols.resolve_id(self.current_scope(), name) {
965 self.symbols.mark_exported(id);
966 }
967 }
968 }
969 }
970
971 fn check_loop_keyword(&mut self, keyword: &str, loc: Loc) {
972 if self.loop_depth == 0 {
973 self.emit(
974 "break-outside-loop",
975 loc,
976 format!("`{keyword}` is only valid inside a loop"),
977 );
978 }
979 }
980
981 fn check_assign_target(&mut self, target: &Expr) {
983 match target {
984 Expr::Variable { name, loc } => match self.resolve(name) {
985 Some(SymbolKind::Var) => self.record_use(name, *loc),
986 Some(other) => self.emit(
987 "invalid-assignment",
988 *loc,
989 format!("cannot assign to `{name}`, it is a {}", other.noun()),
990 ),
991 None if self.is_builtin(name) => self.emit(
992 "reassign-builtin",
993 *loc,
994 format!("cannot reassign built-in `{name}`"),
995 ),
996 None => self.emit(
997 "invalid-assignment",
998 *loc,
999 format!(
1000 "cannot assign to undeclared variable `{name}` (declare it with `=` first)"
1001 ),
1002 ),
1003 },
1004 other => self.check_expr(other),
1006 }
1007 }
1008
1009 fn check_expr(&mut self, expr: &Expr) {
1010 match expr {
1011 Expr::Variable { name, loc } => {
1012 if self.resolve(name).is_none() && !self.is_builtin(name) {
1013 self.emit(
1014 "undeclared-variable",
1015 *loc,
1016 format!("undeclared variable `{name}`"),
1017 );
1018 } else {
1019 self.record_use(name, *loc);
1020 }
1021 }
1022 Expr::Call {
1023 callee, args, loc, ..
1024 } => {
1025 if let Expr::Variable {
1026 name: fname,
1027 loc: fname_loc,
1028 } = callee.as_ref()
1029 {
1030 self.record_use(fname, *fname_loc);
1031 if is_global_only(fname) && self.current_scope() != SymbolTable::GLOBAL {
1032 self.emit(
1033 "global-scope-required",
1034 *loc,
1035 format!("`{fname}` may only be called in the global scope"),
1036 );
1037 }
1038 if SCRIPT_DECLARATIONS.contains(&fname.as_str()) {
1039 self.declarations += 1;
1040 if fname == "library" {
1041 self.library_declared = true;
1042 }
1043 if self.declarations > 1 {
1044 self.emit(
1045 "duplicate-declaration",
1046 *loc,
1047 "a script may only have one indicator/strategy/library declaration",
1048 );
1049 }
1050 }
1051 match self.resolve(fname) {
1052 Some(SymbolKind::Function) => {
1053 if let Some(caller) = self.fn_stack.last() {
1056 self.call_edges.push((caller.clone(), fname.clone(), *loc));
1057 }
1058 if let Some(&(required, total)) = self.functions.get(fname) {
1059 self.check_call_arity(fname, args.len(), required, total, *loc);
1060 }
1061 }
1062 Some(kind @ (SymbolKind::Var | SymbolKind::Type | SymbolKind::Enum)) => {
1064 self.emit(
1065 "not-callable",
1066 *loc,
1067 format!("`{fname}` is a {}, not a function", kind.noun()),
1068 );
1069 }
1070 Some(SymbolKind::Import) => {}
1072 None => {
1073 if !self.is_builtin(fname) {
1074 self.emit(
1075 "unknown-function",
1076 *loc,
1077 format!("unknown function `{fname}`"),
1078 );
1079 }
1080 }
1081 }
1082 } else {
1083 self.check_expr(callee);
1084 }
1085 if let Some(signature) = self.builtin_signature(callee) {
1086 let name = callee_name(callee);
1087 self.check_builtin_args(&name, signature, args, *loc);
1088 }
1089 for arg in args {
1090 match arg {
1091 Argument::Positional(e) => self.check_expr(e),
1092 Argument::Named { value, .. } => self.check_expr(value),
1093 }
1094 }
1095 }
1096 Expr::Binary { left, right, .. } => {
1097 self.check_expr(left);
1098 self.check_expr(right);
1099 }
1100 Expr::Unary { expr, .. } => self.check_expr(expr),
1101 Expr::Index { expr, index, .. } => {
1102 self.check_expr(expr);
1103 self.check_expr(index);
1104 }
1105 Expr::MemberAccess {
1107 object,
1108 member,
1109 member_loc,
1110 } => {
1111 self.check_expr(object);
1112 if let Some(id) = self.resolve_member(object, member) {
1113 let file = self.current_file();
1114 self.symbols.record_use(file, member_loc.position(), id);
1115 } else if self.unknown_member(object, member) {
1116 if let Expr::Variable { name, .. } = object.as_ref() {
1117 self.emit(
1118 "unknown-member",
1119 *member_loc,
1120 format!("`{name}` has no member `{member}`"),
1121 );
1122 }
1123 }
1124 }
1125 Expr::Ternary {
1126 condition,
1127 then_expr,
1128 else_expr,
1129 } => {
1130 self.check_expr(condition);
1131 self.check_expr(then_expr);
1132 self.check_expr(else_expr);
1133 }
1134 Expr::IfExpr {
1135 condition,
1136 then_expr,
1137 else_if_branches,
1138 else_expr,
1139 } => {
1140 self.check_expr(condition);
1141 self.check_expr(then_expr);
1142 for (cond, e) in else_if_branches {
1143 self.check_expr(cond);
1144 self.check_expr(e);
1145 }
1146 if let Some(e) = else_expr {
1147 self.check_expr(e);
1148 }
1149 }
1150 Expr::Switch { value, cases } => {
1151 self.check_expr(value);
1152 for (pattern, result) in cases {
1153 self.check_expr(pattern);
1154 self.check_expr(result);
1155 }
1156 }
1157 Expr::Array(elements) => {
1158 for e in elements {
1159 self.check_expr(e);
1160 }
1161 }
1162 Expr::Function { params, body } => {
1164 self.function_body(
1165 params.iter().map(|p| {
1166 (
1167 p.name.as_str(),
1168 p.default_value.as_ref(),
1169 p.loc,
1170 p.type_annotation.as_ref(),
1171 )
1172 }),
1173 body,
1174 );
1175 }
1176 Expr::Literal(_) => {}
1177 }
1178 }
1179}