Skip to main content

leekscript_analysis/
scope_builder.rs

1//! Scope-building pass: walk the tree and build the scope chain with variables, functions, classes.
2
3use sipha::red::SyntaxNode;
4use sipha::types::IntoSyntaxKind;
5use sipha::walk::{Visitor, WalkResult};
6
7use leekscript_core::syntax::Kind;
8
9use leekscript_core::Type;
10
11use super::node_helpers::{
12    class_decl_info, class_field_info, class_member_visibility, class_method_is_static,
13    for_in_loop_vars, function_decl_info, param_name, var_decl_info,
14};
15use super::scope::{ScopeId, ScopeKind, ScopeStore, VariableInfo, VariableKind};
16use super::type_expr::{
17    find_type_expr_child, param_and_return_types, parse_type_expr, TypeExprResult,
18};
19
20/// Builds scope tree by walking the syntax tree; maintains a stack of scope IDs.
21/// Records the sequence of scope IDs pushed (in walk order) so Validator can use the same IDs.
22pub struct ScopeBuilder {
23    pub store: ScopeStore,
24    stack: Vec<ScopeId>,
25    /// Scope IDs in the order they were pushed (for Validator sync).
26    pub scope_id_sequence: Vec<ScopeId>,
27    /// Class name at each nesting level (for recording fields/methods).
28    class_stack: Vec<String>,
29    /// Root node (set on first enter) for static method detection.
30    root: Option<SyntaxNode>,
31}
32
33impl ScopeBuilder {
34    #[must_use]
35    pub fn new() -> Self {
36        let store = ScopeStore::new();
37        let stack = vec![store.root_id()];
38        Self {
39            store,
40            stack,
41            scope_id_sequence: Vec::new(),
42            class_stack: Vec::new(),
43            root: None,
44        }
45    }
46
47    /// Build scope from a program tree using an existing store (e.g. pre-seeded from signature files).
48    #[must_use]
49    pub fn with_store(store: ScopeStore) -> Self {
50        let root_id = store.root_id();
51        Self {
52            store,
53            stack: vec![root_id],
54            scope_id_sequence: Vec::new(),
55            class_stack: Vec::new(),
56            root: None,
57        }
58    }
59
60    fn current(&self) -> Option<ScopeId> {
61        self.stack.last().copied()
62    }
63
64    fn push(&mut self, kind: ScopeKind) {
65        // On partial/malformed trees, leave_node can pop more than enter_node pushed; keep stack non-empty.
66        if self.stack.is_empty() {
67            self.stack.push(self.store.root_id());
68        }
69        let parent = self.current().unwrap_or_else(|| self.store.root_id());
70        let id = self.store.push(kind, parent);
71        self.stack.push(id);
72        self.scope_id_sequence.push(id);
73    }
74
75    fn pop(&mut self) {
76        if self.stack.len() > 1 {
77            self.stack.pop();
78        }
79    }
80
81    fn main_scope(&self) -> ScopeId {
82        self.store.root_id()
83    }
84
85    /// True when we're inside a class body (so we're building a method, not a top-level function).
86    fn in_class_scope(&self) -> bool {
87        self.stack.iter().any(|&id| {
88            self.store
89                .get(id)
90                .is_some_and(|s| s.kind == ScopeKind::Class)
91        })
92    }
93}
94
95impl Default for ScopeBuilder {
96    fn default() -> Self {
97        Self::new()
98    }
99}
100
101/// True if `node` is at program top level (not inside Block, `FunctionDecl`, or `ClassDecl`).
102fn is_top_level(node: &SyntaxNode, root: &SyntaxNode) -> bool {
103    for anc in node.ancestors(root) {
104        if let Some(Kind::NodeBlock | Kind::NodeFunctionDecl | Kind::NodeClassDecl) =
105            anc.kind_as::<Kind>()
106        {
107            return false;
108        }
109    }
110    true
111}
112
113/// Seed the root scope from a program AST (top-level classes, functions, globals only).
114/// Also registers class fields and methods so member access (e.g. `myCell.x`) infers types.
115/// Used when building scope from included files so the main file sees their declarations.
116pub fn seed_scope_from_program(store: &mut ScopeStore, root: &SyntaxNode) {
117    for kind in [
118        Kind::NodeClassDecl,
119        Kind::NodeFunctionDecl,
120        Kind::NodeVarDecl,
121    ] {
122        for node in root.find_all_nodes(kind.into_syntax_kind()) {
123            if !is_top_level(&node, root) {
124                continue;
125            }
126            match node.kind_as::<Kind>() {
127                Some(Kind::NodeClassDecl) => {
128                    if let Some(info) = class_decl_info(&node) {
129                        store.add_root_class(info.name.clone(), info.name_span);
130                    }
131                }
132                Some(Kind::NodeFunctionDecl) => {
133                    if let Some(info) = function_decl_info(&node) {
134                        let (param_types, return_type) =
135                            param_and_return_types(&node, Kind::NodeParam);
136                        if let (Some(pt), Some(rt)) = (param_types, return_type) {
137                            store.add_root_function_with_types(
138                                info.name.clone(),
139                                info.min_arity,
140                                info.max_arity,
141                                info.name_span,
142                                Some(pt),
143                                Some(rt),
144                            );
145                        } else {
146                            store.add_root_function(
147                                info.name.clone(),
148                                info.min_arity,
149                                info.max_arity,
150                                info.name_span,
151                            );
152                        }
153                    }
154                }
155                Some(Kind::NodeVarDecl) => {
156                    if let Some(info) = var_decl_info(&node) {
157                        if info.kind == super::node_helpers::VarDeclKind::Global {
158                            let declared_type = find_type_expr_child(&node).and_then(|te| {
159                                match parse_type_expr(&te) {
160                                    TypeExprResult::Ok(ty) => Some(ty),
161                                    TypeExprResult::Err(_) => None,
162                                }
163                            });
164                            if let Some(ty) = declared_type {
165                                store.add_root_global_with_type(info.name.clone(), ty);
166                            } else {
167                                store.add_root_global(info.name.clone());
168                            }
169                        }
170                    }
171                }
172                _ => {}
173            }
174        }
175    }
176    seed_class_members_from_program(store, root);
177}
178
179/// Register class fields and methods from a program AST so that member access (obj.field, `obj.method()`)
180/// infers types. Called after root-level decls are seeded; only processes top-level classes.
181fn seed_class_members_from_program(store: &mut ScopeStore, root: &SyntaxNode) {
182    let kind_class = Kind::NodeClassDecl.into_syntax_kind();
183    let class_decls: Vec<SyntaxNode> = root.find_all_nodes(kind_class);
184
185    for node in root.find_all_nodes(Kind::NodeClassField.into_syntax_kind()) {
186        let node_range = node.text_range();
187        let anc = class_decls.iter().find(|c| {
188            let r = c.text_range();
189            r.start <= node_range.start && node_range.end <= r.end
190        });
191        let class_name = match anc.and_then(class_decl_info) {
192            Some(info) => info.name,
193            None => continue,
194        };
195        if let Some((field_name, ty, is_static)) = class_field_info(&node) {
196            let ty = ty.unwrap_or(Type::any());
197            let vis = class_member_visibility(&node, root);
198            if is_static {
199                store.add_class_static_field(&class_name, field_name, ty, vis);
200            } else {
201                store.add_class_field(&class_name, field_name, ty, vis);
202            }
203        }
204    }
205
206    for node in root.find_all_nodes(Kind::NodeFunctionDecl.into_syntax_kind()) {
207        let node_range = node.text_range();
208        let anc = class_decls.iter().find(|c| {
209            let r = c.text_range();
210            r.start <= node_range.start && node_range.end <= r.end
211        });
212        let class_name = match anc.and_then(class_decl_info) {
213            Some(info) => info.name,
214            None => continue,
215        };
216        if let Some(info) = function_decl_info(&node) {
217            let (param_types, return_type) = param_and_return_types(&node, Kind::NodeParam);
218            let params = param_types.unwrap_or_default();
219            let ret = return_type.unwrap_or(Type::any());
220            let is_static = class_method_is_static(&node, root);
221            let vis = class_member_visibility(&node, root);
222            if is_static {
223                store.add_class_static_method(&class_name, info.name, params, ret, vis);
224            } else {
225                store.add_class_method(&class_name, info.name, params, ret, vis);
226            }
227        }
228    }
229}
230
231impl Visitor for ScopeBuilder {
232    fn enter_node(&mut self, node: &SyntaxNode) -> WalkResult {
233        if self.root.is_none() {
234            self.root = Some(node.clone());
235        }
236        let kind = match node.kind_as::<Kind>() {
237            Some(k) => k,
238            None => return WalkResult::Continue(()),
239        };
240
241        match kind {
242            Kind::NodeBlock => {
243                self.push(ScopeKind::Block);
244            }
245            Kind::NodeFunctionDecl => {
246                if self.in_class_scope() {
247                    if let Some(class_name) = self.class_stack.last().cloned() {
248                        if let Some(info) = function_decl_info(node) {
249                            let (param_types, return_type) =
250                                param_and_return_types(node, Kind::NodeParam);
251                            let params = param_types.unwrap_or_default();
252                            // A constructor returns an instance of the class when no return type is given.
253                            let ret = return_type.unwrap_or_else(|| {
254                                if info.name == class_name {
255                                    Type::instance(class_name.clone())
256                                } else {
257                                    Type::any()
258                                }
259                            });
260                            let is_static = self
261                                .root
262                                .as_ref()
263                                .is_some_and(|root| class_method_is_static(node, root));
264                            let vis = class_member_visibility(node, self.root.as_ref().unwrap());
265                            if is_static {
266                                self.store.add_class_static_method(
267                                    &class_name,
268                                    info.name,
269                                    params,
270                                    ret,
271                                    vis,
272                                );
273                            } else {
274                                self.store.add_class_method(
275                                    &class_name,
276                                    info.name,
277                                    params,
278                                    ret,
279                                    vis,
280                                );
281                            }
282                        }
283                    }
284                } else {
285                    // Only register in main scope for top-level functions, not class methods.
286                    if let Some(info) = function_decl_info(node) {
287                        if let Some(main_scope) = self.store.get_mut(self.main_scope()) {
288                            let (param_types, return_type) =
289                                param_and_return_types(node, Kind::NodeParam);
290                            if param_types.is_some() || return_type.is_some() {
291                                main_scope.add_function_with_types(
292                                    info.name.clone(),
293                                    info.min_arity,
294                                    info.max_arity,
295                                    info.name_span,
296                                    param_types,
297                                    return_type,
298                                );
299                            } else {
300                                main_scope.add_function(
301                                    info.name.clone(),
302                                    info.min_arity,
303                                    info.max_arity,
304                                    info.name_span,
305                                );
306                            }
307                        }
308                    }
309                }
310                self.push(ScopeKind::Function);
311            }
312            Kind::NodeClassDecl => {
313                if let Some(info) = class_decl_info(node) {
314                    if let Some(main_scope) = self.store.get_mut(self.main_scope()) {
315                        main_scope.add_class(info.name.clone(), info.name_span);
316                    }
317                    self.class_stack.push(info.name.clone());
318                }
319                self.push(ScopeKind::Class);
320            }
321            Kind::NodeConstructorDecl => {
322                // Push Function scope so constructor params are in scope for the body (like methods).
323                self.push(ScopeKind::Function);
324            }
325            Kind::NodeClassField => {
326                if let Some(class_name) = self.class_stack.last() {
327                    if let Some((field_name, ty, is_static)) = class_field_info(node) {
328                        let ty = ty.unwrap_or(Type::any());
329                        let vis = class_member_visibility(node, self.root.as_ref().unwrap());
330                        if is_static {
331                            self.store
332                                .add_class_static_field(class_name, field_name, ty, vis);
333                        } else {
334                            self.store.add_class_field(class_name, field_name, ty, vis);
335                        }
336                    }
337                }
338            }
339            Kind::NodeWhileStmt
340            | Kind::NodeForStmt
341            | Kind::NodeForInStmt
342            | Kind::NodeDoWhileStmt => {
343                self.push(ScopeKind::Loop);
344                if matches!(kind, Kind::NodeForInStmt) {
345                    for (name, span) in for_in_loop_vars(node) {
346                        if let Some(current_id) = self.current() {
347                            if let Some(scope) = self.store.get_mut(current_id) {
348                                scope.add_variable(VariableInfo {
349                                    name,
350                                    kind: VariableKind::Local,
351                                    span,
352                                    declared_type: None,
353                                });
354                            }
355                        }
356                    }
357                }
358            }
359            Kind::NodeVarDecl => {
360                if let Some(info) = var_decl_info(node) {
361                    // For "var x = ..." do not parse a type (avoids "var" keyword as type).
362                    let declared_type = if info.kind == super::node_helpers::VarDeclKind::Var {
363                        None
364                    } else {
365                        find_type_expr_child(node).and_then(|te| match parse_type_expr(&te) {
366                            TypeExprResult::Ok(t) => Some(t),
367                            TypeExprResult::Err(_) => None,
368                        })
369                    };
370                    let var_kind = match info.kind {
371                        super::node_helpers::VarDeclKind::Global => {
372                            if let Some(main_scope) = self.store.get_mut(self.main_scope()) {
373                                main_scope.add_global(info.name.clone());
374                            }
375                            VariableKind::Global
376                        }
377                        _ => VariableKind::Local,
378                    };
379                    if let Some(current_id) = self.current() {
380                        if let Some(scope) = self.store.get_mut(current_id) {
381                            scope.add_variable(VariableInfo {
382                                name: info.name,
383                                kind: var_kind,
384                                span: info.name_span,
385                                declared_type,
386                            });
387                        }
388                    }
389                }
390            }
391            Kind::NodeParam => {
392                if let Some((name, span)) = param_name(node) {
393                    let declared_type =
394                        find_type_expr_child(node).and_then(|te| match parse_type_expr(&te) {
395                            TypeExprResult::Ok(t) => Some(t),
396                            TypeExprResult::Err(_) => None,
397                        });
398                    if let Some(current_id) = self.current() {
399                        if let Some(scope) = self.store.get_mut(current_id) {
400                            scope.add_variable(VariableInfo {
401                                name,
402                                kind: VariableKind::Parameter,
403                                span,
404                                declared_type,
405                            });
406                        }
407                    }
408                }
409            }
410            _ => {}
411        }
412
413        WalkResult::Continue(())
414    }
415
416    fn leave_node(&mut self, node: &SyntaxNode) -> WalkResult {
417        let kind = match node.kind_as::<Kind>() {
418            Some(k) => k,
419            None => return WalkResult::Continue(()),
420        };
421
422        match kind {
423            Kind::NodeBlock
424            | Kind::NodeFunctionDecl
425            | Kind::NodeClassDecl
426            | Kind::NodeConstructorDecl
427            | Kind::NodeWhileStmt
428            | Kind::NodeForStmt
429            | Kind::NodeForInStmt
430            | Kind::NodeDoWhileStmt => {
431                if kind == Kind::NodeClassDecl {
432                    self.class_stack.pop();
433                }
434                self.pop();
435            }
436            _ => {}
437        }
438
439        WalkResult::Continue(())
440    }
441}