leekscript_analysis/
validator.rs1use sipha::error::SemanticDiagnostic;
4use sipha::red::SyntaxNode;
5use sipha::types::Span;
6use sipha::walk::{Visitor, WalkResult};
7use std::collections::HashMap;
8
9use leekscript_core::syntax::Kind;
10
11use super::error::AnalysisError;
12use super::node_helpers::{
13 class_decl_info, expr_identifier, for_in_loop_vars, function_decl_info, param_name,
14 var_decl_info,
15};
16use super::scope::{ScopeId, ScopeKind, ScopeStore};
17
18pub struct Validator<'a> {
21 pub store: &'a ScopeStore,
22 stack: Vec<ScopeId>,
23 declared_in_scope: Vec<HashMap<String, Span>>,
25 scope_id_index: usize,
27 scope_id_sequence: &'a [ScopeId],
29 pub diagnostics: Vec<SemanticDiagnostic>,
30}
31
32impl<'a> Validator<'a> {
33 #[must_use]
34 pub fn new(store: &'a ScopeStore, scope_id_sequence: &'a [ScopeId]) -> Self {
35 Self {
36 store,
37 stack: vec![ScopeId(0)],
38 declared_in_scope: vec![HashMap::new()],
39 scope_id_index: 0,
40 scope_id_sequence,
41 diagnostics: Vec::new(),
42 }
43 }
44
45 fn current_scope(&self) -> ScopeId {
46 *self.stack.last().unwrap_or(&ScopeId(0))
47 }
48
49 fn push_scope(&mut self) {
50 let id = self
51 .scope_id_sequence
52 .get(self.scope_id_index)
53 .copied()
54 .unwrap_or_else(|| ScopeId(self.scope_id_index + 1));
55 self.scope_id_index += 1;
56 self.stack.push(id);
57 self.declared_in_scope.push(HashMap::new());
58 }
59
60 fn pop_scope(&mut self) {
61 if self.stack.len() > 1 {
62 self.stack.pop();
63 self.declared_in_scope.pop();
64 }
65 }
66
67 fn resolve(&self, name: &str) -> bool {
68 if self.store.resolve(self.current_scope(), name).is_some() {
69 return true;
70 }
71 self.declared_in_scope
74 .iter()
75 .any(|map| map.contains_key(name))
76 }
77
78 fn in_loop(&self) -> bool {
79 self.stack.iter().rev().any(|&id| {
80 self.store
81 .get(id)
82 .is_some_and(|s| s.kind == ScopeKind::Loop)
83 })
84 }
85
86 fn in_main_block(&self) -> bool {
88 self.stack.iter().all(|&id| {
89 self.store
90 .get(id)
91 .is_none_or(|s| s.kind == ScopeKind::Main || s.kind == ScopeKind::Block)
92 })
93 }
94
95 fn in_function_scope(&self) -> bool {
97 self.stack.iter().any(|&id| {
98 self.store
99 .get(id)
100 .is_some_and(|s| s.kind == ScopeKind::Function)
101 })
102 }
103
104 fn in_class_scope(&self) -> bool {
106 self.stack.iter().any(|&id| {
107 self.store
108 .get(id)
109 .is_some_and(|s| s.kind == ScopeKind::Class)
110 })
111 }
112
113 fn in_method_scope(&self) -> bool {
115 self.in_class_scope() && self.in_function_scope()
116 }
117}
118
119impl Visitor for Validator<'_> {
120 fn enter_node(&mut self, node: &SyntaxNode) -> WalkResult {
121 let kind = match node.kind_as::<Kind>() {
122 Some(k) => k,
123 None => return WalkResult::Continue(()),
124 };
125
126 match kind {
127 Kind::NodeBlock
128 | Kind::NodeWhileStmt
129 | Kind::NodeForStmt
130 | Kind::NodeForInStmt
131 | Kind::NodeDoWhileStmt => {
132 self.push_scope();
133 if matches!(kind, Kind::NodeForInStmt) {
134 for (name, span) in for_in_loop_vars(node) {
135 if let Some(declared) = self.declared_in_scope.last_mut() {
136 declared.entry(name).or_insert(span);
137 }
138 }
139 }
140 }
141 Kind::NodeInclude => {
142 if !self.in_main_block() {
143 if let Some(tok) = node.first_token() {
144 self.diagnostics
145 .push(AnalysisError::IncludeOnlyInMainBlock.at(tok.text_range()));
146 }
147 }
148 }
149 Kind::NodeFunctionDecl => {
150 if self.in_function_scope() {
152 if let Some(info) = function_decl_info(node) {
153 self.diagnostics
154 .push(AnalysisError::FunctionOnlyInMainBlock.at(info.name_span));
155 }
156 } else if self.in_main_block() {
157 if let Some(info) = function_decl_info(node) {
159 if info.min_arity < info.max_arity {
160 self.diagnostics.push(
161 AnalysisError::OptionalParamsOnlyInStandardFunctionsOrMethods
162 .at(info.name_span),
163 );
164 }
165 if let Some(main) = self.store.get(self.store.root_id()) {
167 if let Some(existing_span) = main.get_function_span_for_arity_range(
168 &info.name,
169 info.min_arity,
170 info.max_arity,
171 ) {
172 if existing_span != info.name_span
173 && existing_span != Span::new(0, 0)
174 {
175 self.diagnostics.push(
176 AnalysisError::DuplicateFunctionName.at_with_related(
177 info.name_span,
178 vec![(existing_span, "first declared here")],
179 ),
180 );
181 }
182 }
183 }
184 }
185 }
186 self.push_scope();
187 }
188 Kind::NodeClassDecl => {
189 if let Some(info) = class_decl_info(node) {
190 if let Some(main) = self.store.get(self.store.root_id()) {
191 if let Some(first_span) = main.get_class_first_span(&info.name) {
192 if first_span != info.name_span && first_span != Span::new(0, 0) {
193 self.diagnostics.push(
194 AnalysisError::DuplicateClassName.at_with_related(
195 info.name_span,
196 vec![(first_span, "first declared here")],
197 ),
198 );
199 }
200 }
201 }
202 }
203 self.push_scope();
204 }
205 Kind::NodeConstructorDecl => {
206 self.push_scope();
207 }
208 Kind::NodeParam => {
209 if let Some((name, span)) = param_name(node) {
210 if let Some(declared) = self.declared_in_scope.last_mut() {
211 declared.entry(name).or_insert(span);
212 }
213 }
214 }
215 Kind::NodeVarDecl => {
216 if let Some(info) = var_decl_info(node) {
217 if info.kind == super::node_helpers::VarDeclKind::Global
218 && !self.in_main_block()
219 {
220 self.diagnostics
221 .push(AnalysisError::GlobalOnlyInMainBlock.at(info.name_span));
222 }
223 if let Some(declared) = self.declared_in_scope.last_mut() {
224 match declared.entry(info.name.clone()) {
225 std::collections::hash_map::Entry::Occupied(entry) => {
226 let first_span = *entry.get();
227 self.diagnostics.push(
228 AnalysisError::VariableNameUnavailable.at_with_related(
229 info.name_span,
230 vec![(first_span, "first declared here")],
231 ),
232 );
233 }
234 std::collections::hash_map::Entry::Vacant(entry) => {
235 entry.insert(info.name_span);
236 }
237 }
238 }
239 }
240 }
241 Kind::NodePrimaryExpr => {
242 if let Some((name, span)) = expr_identifier(node) {
243 if name == "this" && self.in_method_scope() {
244 } else if !self.resolve(&name) {
246 self.diagnostics
247 .push(AnalysisError::UnknownVariableOrFunction.at(span));
248 }
249 }
250 }
251 Kind::NodeBreakStmt => {
252 if !self.in_loop() {
253 if let Some(tok) = node.first_token() {
254 self.diagnostics
255 .push(AnalysisError::BreakOutOfLoop.at(tok.text_range()));
256 }
257 }
258 }
259 Kind::NodeContinueStmt => {
260 if !self.in_loop() {
261 if let Some(tok) = node.first_token() {
262 self.diagnostics
263 .push(AnalysisError::ContinueOutOfLoop.at(tok.text_range()));
264 }
265 }
266 }
267 _ => {}
268 }
269
270 WalkResult::Continue(())
271 }
272
273 fn leave_node(&mut self, node: &SyntaxNode) -> WalkResult {
274 let kind = match node.kind_as::<Kind>() {
275 Some(k) => k,
276 None => return WalkResult::Continue(()),
277 };
278
279 match kind {
280 Kind::NodeBlock
281 | Kind::NodeFunctionDecl
282 | Kind::NodeClassDecl
283 | Kind::NodeConstructorDecl
284 | Kind::NodeWhileStmt
285 | Kind::NodeForStmt
286 | Kind::NodeForInStmt
287 | Kind::NodeDoWhileStmt => {
288 self.pop_scope();
289 }
290 _ => {}
291 }
292
293 WalkResult::Continue(())
294 }
295}