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