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