Skip to main content

mlua_check/
scope.rs

1use std::collections::HashMap;
2
3/// Tracks variable definitions and references across nested scopes.
4///
5/// Each scope level maintains its own set of local definitions.
6/// Variable lookups walk outward from the innermost scope.
7#[derive(Debug)]
8pub struct ScopeStack {
9    /// Stack of scopes, innermost last.
10    scopes: Vec<Scope>,
11}
12
13#[derive(Debug)]
14struct Scope {
15    /// Local variable names defined in this scope → (line, column, referenced).
16    locals: HashMap<String, LocalDef>,
17}
18
19#[derive(Debug)]
20pub struct LocalDef {
21    pub line: usize,
22    pub column: usize,
23    pub referenced: bool,
24    /// Optional LuaCats class type (from `---@param` or `---@type`).
25    pub class_type: Option<String>,
26}
27
28impl Default for ScopeStack {
29    fn default() -> Self {
30        Self::new()
31    }
32}
33
34impl ScopeStack {
35    pub fn new() -> Self {
36        Self {
37            scopes: vec![Scope {
38                locals: HashMap::new(),
39            }],
40        }
41    }
42
43    /// Enter a new scope (function body, block, etc.).
44    pub fn push_scope(&mut self) {
45        self.scopes.push(Scope {
46            locals: HashMap::new(),
47        });
48    }
49
50    /// Leave the current scope. Returns unreferenced locals for
51    /// unused-variable detection.
52    pub fn pop_scope(&mut self) -> Vec<(String, LocalDef)> {
53        let scope = self.scopes.pop().unwrap_or(Scope {
54            locals: HashMap::new(),
55        });
56        scope
57            .locals
58            .into_iter()
59            .filter(|(_, def)| !def.referenced)
60            .collect()
61    }
62
63    /// Define a local variable in the current scope.
64    pub fn define_local(&mut self, name: &str, line: usize, column: usize) {
65        if let Some(scope) = self.scopes.last_mut() {
66            scope.locals.insert(
67                name.to_string(),
68                LocalDef {
69                    line,
70                    column,
71                    referenced: false,
72                    class_type: None,
73                },
74            );
75        }
76    }
77
78    /// Define a local variable with an associated LuaCats class type.
79    pub fn define_local_typed(
80        &mut self,
81        name: &str,
82        line: usize,
83        column: usize,
84        class_type: String,
85    ) {
86        if let Some(scope) = self.scopes.last_mut() {
87            scope.locals.insert(
88                name.to_string(),
89                LocalDef {
90                    line,
91                    column,
92                    referenced: false,
93                    class_type: Some(class_type),
94                },
95            );
96        }
97    }
98
99    /// Look up the class type of a local variable (if any).
100    pub fn class_type_of(&self, name: &str) -> Option<&str> {
101        for scope in self.scopes.iter().rev() {
102            if let Some(def) = scope.locals.get(name) {
103                return def.class_type.as_deref();
104            }
105        }
106        None
107    }
108
109    /// Check whether a name is defined in any enclosing scope.
110    /// If found, marks it as referenced.
111    pub fn resolve_and_mark(&mut self, name: &str) -> bool {
112        for scope in self.scopes.iter_mut().rev() {
113            if let Some(def) = scope.locals.get_mut(name) {
114                def.referenced = true;
115                return true;
116            }
117        }
118        false
119    }
120
121    /// Check whether a name is defined without marking it.
122    pub fn is_defined(&self, name: &str) -> bool {
123        self.scopes
124            .iter()
125            .rev()
126            .any(|scope| scope.locals.contains_key(name))
127    }
128}