Skip to main content

lisette_semantics/checker/
scopes.rs

1use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
2use std::cell::Cell;
3use syntax::ast::BindingId;
4use syntax::ast::Span;
5use syntax::types::{Symbol, Type};
6
7#[derive(Debug, Clone, Default)]
8pub struct DepthCounter(Cell<usize>);
9
10impl DepthCounter {
11    pub fn new() -> Self {
12        Self(Cell::new(0))
13    }
14    pub fn with_value(n: usize) -> Self {
15        Self(Cell::new(n))
16    }
17    pub fn get(&self) -> usize {
18        self.0.get()
19    }
20    pub fn increment(&self) {
21        self.0.set(self.0.get() + 1);
22    }
23    pub fn decrement(&self) {
24        self.0.set(self.0.get().saturating_sub(1));
25    }
26    pub fn is_active(&self) -> bool {
27        self.0.get() > 0
28    }
29    pub fn reset(&self) -> usize {
30        let prev = self.0.get();
31        self.0.set(0);
32        prev
33    }
34    pub fn restore(&self, depth: usize) {
35        self.0.set(depth);
36    }
37}
38
39#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
40pub enum UseContext {
41    #[default]
42    Statement,
43    Value,
44    Callee,
45    AssignmentTarget,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq)]
49pub enum CarrierKind {
50    Result,
51    Option,
52}
53
54#[derive(Debug, Clone)]
55pub struct TryBlockContext {
56    pub ok_ty: Type,
57    pub err_ty: Type,
58    pub carrier: Cell<Option<CarrierKind>>,
59    pub has_question_mark: Cell<bool>,
60    pub try_span: Span,
61    pub loop_depth: DepthCounter,
62}
63
64#[derive(Debug, Clone)]
65pub struct RecoverBlockContext {
66    pub inner_ty: Type,
67    pub recover_span: Span,
68    pub loop_depth: DepthCounter,
69}
70
71#[derive(Debug, Clone)]
72pub struct Scope {
73    /// variable name -> type
74    pub values: HashMap<String, Type>,
75    pub mutables: Option<HashSet<String>>,
76    pub consts: Option<HashSet<String>>,
77    pub type_params: Option<HashMap<String, usize>>,
78    pub trait_bounds: Option<HashMap<Symbol, Vec<Type>>>,
79    pub fn_return_type: Option<Type>,
80    pub try_block_context: Option<TryBlockContext>,
81    pub recover_block_context: Option<RecoverBlockContext>,
82    pub loop_break_type: Option<Type>,
83    pub loop_depth: DepthCounter,
84    pub defer_block_depth: DepthCounter,
85    pub negation_depth: DepthCounter,
86    pub type_param_depth: DepthCounter,
87    pub use_context: Cell<UseContext>,
88    /// variable name -> binding ID (for linting)
89    pub name_to_binding: HashMap<String, BindingId>,
90}
91
92impl Default for Scope {
93    fn default() -> Self {
94        Self::new()
95    }
96}
97
98impl Scope {
99    pub fn new() -> Self {
100        Scope {
101            values: HashMap::default(),
102            mutables: None,
103            consts: None,
104            type_params: None,
105            trait_bounds: None,
106            fn_return_type: None,
107            try_block_context: None,
108            recover_block_context: None,
109            loop_break_type: None,
110            loop_depth: DepthCounter::new(),
111            defer_block_depth: DepthCounter::new(),
112            negation_depth: DepthCounter::new(),
113            type_param_depth: DepthCounter::new(),
114            use_context: Cell::new(UseContext::Statement),
115            name_to_binding: HashMap::default(),
116        }
117    }
118}
119
120pub struct Scopes {
121    stack: Vec<Scope>,
122    /// True when inferring the body of a match/select arm. Consumed by
123    /// `infer_break`/`infer_continue` to decide whether the enclosing loop
124    /// needs a Go label (since Go switch cases do not fall through).
125    in_match_arm: Cell<bool>,
126    /// One entry per enclosing loop; set to `true` when a break/continue is
127    /// encountered inside a match arm. The top is popped by the loop's
128    /// inference function and recorded on the Loop/While/For/WhileLet AST node.
129    loop_needs_label_stack: std::cell::RefCell<Vec<bool>>,
130    /// True when inferring inside a compound expression (call arg, binary
131    /// operand, etc.). Used to reject `Err(x)?`/`None?` and similar control-flow
132    /// in positions where they can never produce a value.
133    in_subexpression: Cell<bool>,
134    /// True when inferring the base of a dot-access chain. Suppresses the
135    /// record-struct-as-value error when the struct name is a type qualifier
136    /// (e.g. `lib.Point` in `lib.Point.sum`).
137    dot_access_base: Cell<bool>,
138    /// True while inferring a `let` binding's right-hand side. Suppresses the generic
139    /// "used as a value" rejection there so `bindings.rs` can raise the specific
140    /// "cannot bind a type or module to a variable" error instead.
141    let_binding_rhs: Cell<bool>,
142    /// The enclosing impl block's receiver type, used to resolve `self`
143    /// parameter annotations inside the impl's methods. `None` outside impls.
144    /// Singleton because Lisette does not allow nested impl blocks.
145    impl_receiver_type: Option<Type>,
146}
147
148impl Default for Scopes {
149    fn default() -> Self {
150        Self::new()
151    }
152}
153
154impl Scopes {
155    pub fn new() -> Self {
156        Scopes {
157            stack: vec![Scope::new()],
158            in_match_arm: Cell::new(false),
159            loop_needs_label_stack: std::cell::RefCell::new(Vec::new()),
160            in_subexpression: Cell::new(false),
161            dot_access_base: Cell::new(false),
162            let_binding_rhs: Cell::new(false),
163            impl_receiver_type: None,
164        }
165    }
166
167    pub fn current(&self) -> &Scope {
168        self.stack.last().expect("scope stack must not be empty")
169    }
170
171    pub fn current_mut(&mut self) -> &mut Scope {
172        self.stack
173            .last_mut()
174            .expect("scope stack must not be empty")
175    }
176
177    pub fn push(&mut self) {
178        let current = self.current();
179        let mut scope = Scope::new();
180        scope.loop_break_type = current.loop_break_type.clone();
181        scope.loop_depth = DepthCounter::with_value(current.loop_depth.get());
182        scope.defer_block_depth = DepthCounter::with_value(current.defer_block_depth.get());
183        scope.negation_depth = DepthCounter::with_value(current.negation_depth.get());
184        scope.type_param_depth = DepthCounter::with_value(current.type_param_depth.get());
185        scope.use_context = Cell::new(current.use_context.get());
186        self.stack.push(scope);
187    }
188
189    pub fn pop(&mut self) {
190        if self.stack.len() > 1 {
191            self.stack.pop();
192        }
193    }
194
195    pub fn reset(&mut self) {
196        self.stack.clear();
197        self.stack.push(Scope::new());
198        self.in_match_arm.set(false);
199        self.loop_needs_label_stack.borrow_mut().clear();
200        self.in_subexpression.set(false);
201        self.dot_access_base.set(false);
202        self.impl_receiver_type = None;
203    }
204
205    /// Look up a value by walking the scope stack from top to bottom.
206    pub fn lookup_value(&self, name: &str) -> Option<&Type> {
207        for scope in self.stack.iter().rev() {
208            if let Some(ty) = scope.values.get(name) {
209                return Some(ty);
210            }
211        }
212        None
213    }
214
215    /// Check if a variable is marked mutable in any enclosing scope.
216    pub fn lookup_mutable(&self, name: &str) -> bool {
217        self.stack
218            .iter()
219            .rev()
220            .any(|s| s.mutables.as_ref().is_some_and(|m| m.contains(name)))
221    }
222
223    /// Whether `name` is a block-local `const` in any enclosing scope.
224    pub fn lookup_const(&self, name: &str) -> bool {
225        self.stack
226            .iter()
227            .rev()
228            .any(|s| s.consts.as_ref().is_some_and(|c| c.contains(name)))
229    }
230
231    /// Look up a binding ID by walking the scope stack from top to bottom.
232    pub fn lookup_binding_id(&self, name: &str) -> Option<BindingId> {
233        for scope in self.stack.iter().rev() {
234            if let Some(id) = scope.name_to_binding.get(name) {
235                return Some(*id);
236            }
237        }
238        None
239    }
240
241    /// Look up a type parameter by walking the scope stack from top to bottom.
242    pub fn lookup_type_param(&self, name: &str) -> Option<usize> {
243        for scope in self.stack.iter().rev() {
244            if let Some(idx) = scope.type_params.as_ref().and_then(|tp| tp.get(name)) {
245                return Some(*idx);
246            }
247        }
248        None
249    }
250
251    /// Look up the enclosing function's return type.
252    pub fn lookup_fn_return_type(&self) -> Option<&Type> {
253        for scope in self.stack.iter().rev() {
254            if let Some(ref ty) = scope.fn_return_type {
255                return Some(ty);
256            }
257        }
258        None
259    }
260
261    /// Look up the enclosing try block context, stopping at function boundaries.
262    pub fn lookup_try_block_context(&self) -> Option<&TryBlockContext> {
263        for scope in self.stack.iter().rev() {
264            if scope.try_block_context.is_some() {
265                return scope.try_block_context.as_ref();
266            }
267            if scope.fn_return_type.is_some() {
268                return None;
269            }
270        }
271        None
272    }
273
274    /// Look up the enclosing recover block context, stopping at function boundaries.
275    pub fn lookup_recover_block_context(&self) -> Option<&RecoverBlockContext> {
276        for scope in self.stack.iter().rev() {
277            if scope.recover_block_context.is_some() {
278                return scope.recover_block_context.as_ref();
279            }
280            if scope.fn_return_type.is_some() {
281                return None;
282            }
283        }
284        None
285    }
286
287    pub fn collect_all_value_names(&self) -> Vec<String> {
288        let mut names = Vec::new();
289        for scope in &self.stack {
290            names.extend(scope.values.keys().cloned());
291        }
292        names
293    }
294
295    pub fn collect_all_trait_bounds(&self) -> HashMap<Symbol, Vec<Type>> {
296        let mut all_bounds = HashMap::default();
297        // Walk from bottom to top so inner scopes override outer
298        for scope in &self.stack {
299            if let Some(ref bounds) = scope.trait_bounds {
300                for (key, value) in bounds {
301                    all_bounds.insert(key.clone(), value.clone());
302                }
303            }
304        }
305        all_bounds
306    }
307
308    pub fn for_each_bound_on_param<F: FnMut(&Type)>(&self, param_name: &str, mut visit: F) {
309        for scope in self.stack.iter().rev() {
310            let introduces = scope
311                .type_params
312                .as_ref()
313                .is_some_and(|tp| tp.contains_key(param_name));
314            if !introduces {
315                continue;
316            }
317            if let Some(ref bounds) = scope.trait_bounds {
318                for (key, types) in bounds {
319                    if key.last_segment() == param_name {
320                        for ty in types {
321                            visit(ty);
322                        }
323                    }
324                }
325            }
326            return;
327        }
328    }
329
330    pub fn increment_loop_depth(&self) {
331        self.current().loop_depth.increment();
332    }
333
334    pub fn decrement_loop_depth(&self) {
335        self.current().loop_depth.decrement();
336    }
337
338    pub fn is_inside_loop(&self) -> bool {
339        self.current().loop_depth.is_active()
340    }
341
342    pub fn set_loop_break_type(&mut self, ty: Type) {
343        self.current_mut().loop_break_type = Some(ty);
344    }
345
346    pub fn clear_loop_break_type(&mut self) {
347        self.current_mut().loop_break_type = None;
348    }
349
350    pub fn loop_break_type(&self) -> Option<&Type> {
351        self.current().loop_break_type.as_ref()
352    }
353
354    pub fn increment_defer_block_depth(&self) {
355        self.current().defer_block_depth.increment();
356    }
357
358    pub fn decrement_defer_block_depth(&self) {
359        self.current().defer_block_depth.decrement();
360    }
361
362    pub fn is_inside_defer_block(&self) -> bool {
363        self.current().defer_block_depth.is_active()
364    }
365
366    pub fn defer_block_loop_depth(&self) -> usize {
367        self.current().loop_depth.get()
368    }
369
370    pub fn increment_negation_depth(&self) {
371        self.current().negation_depth.increment();
372    }
373
374    pub fn decrement_negation_depth(&self) {
375        self.current().negation_depth.decrement();
376    }
377
378    pub fn is_inside_negation(&self) -> bool {
379        self.current().negation_depth.is_active()
380    }
381
382    pub fn reset_loop_depth(&self) -> usize {
383        self.current().loop_depth.reset()
384    }
385
386    pub fn restore_loop_depth(&self, depth: usize) {
387        self.current().loop_depth.restore(depth);
388    }
389
390    pub fn set_value_context(&self) -> UseContext {
391        let prev = self.current().use_context.get();
392        self.current().use_context.set(UseContext::Value);
393        prev
394    }
395
396    pub fn set_statement_context(&self) -> UseContext {
397        let prev = self.current().use_context.get();
398        self.current().use_context.set(UseContext::Statement);
399        prev
400    }
401
402    pub fn restore_use_context(&self, ctx: UseContext) {
403        self.current().use_context.set(ctx);
404    }
405
406    pub fn is_value_context(&self) -> bool {
407        self.current().use_context.get() == UseContext::Value
408    }
409
410    pub fn set_callee_context(&self) -> UseContext {
411        let prev = self.current().use_context.get();
412        self.current().use_context.set(UseContext::Callee);
413        prev
414    }
415
416    pub fn is_callee_context(&self) -> bool {
417        self.current().use_context.get() == UseContext::Callee
418    }
419
420    pub fn set_assignment_target_context(&self) -> UseContext {
421        let prev = self.current().use_context.get();
422        self.current().use_context.set(UseContext::AssignmentTarget);
423        prev
424    }
425
426    pub fn is_assignment_target_context(&self) -> bool {
427        self.current().use_context.get() == UseContext::AssignmentTarget
428    }
429
430    pub fn is_in_match_arm(&self) -> bool {
431        self.in_match_arm.get()
432    }
433
434    pub fn set_in_match_arm(&self, value: bool) -> bool {
435        self.in_match_arm.replace(value)
436    }
437
438    pub fn push_loop_needs_label(&self) {
439        self.loop_needs_label_stack.borrow_mut().push(false);
440    }
441
442    pub fn pop_loop_needs_label(&self) -> bool {
443        self.loop_needs_label_stack
444            .borrow_mut()
445            .pop()
446            .expect("loop_needs_label_stack must not be empty when popping")
447    }
448
449    pub fn mark_current_loop_needs_label(&self) {
450        if let Some(flag) = self.loop_needs_label_stack.borrow_mut().last_mut() {
451            *flag = true;
452        }
453    }
454
455    pub fn is_in_subexpression(&self) -> bool {
456        self.in_subexpression.get()
457    }
458
459    pub fn set_in_subexpression(&self, value: bool) -> bool {
460        self.in_subexpression.replace(value)
461    }
462
463    pub fn is_dot_access_base(&self) -> bool {
464        self.dot_access_base.get()
465    }
466
467    pub fn set_dot_access_base(&self, value: bool) -> bool {
468        self.dot_access_base.replace(value)
469    }
470
471    pub fn is_let_binding_rhs(&self) -> bool {
472        self.let_binding_rhs.get()
473    }
474
475    pub fn set_let_binding_rhs(&self, value: bool) -> bool {
476        self.let_binding_rhs.replace(value)
477    }
478
479    pub fn increment_type_param_depth(&self) {
480        self.current().type_param_depth.increment();
481    }
482
483    pub fn decrement_type_param_depth(&self) {
484        self.current().type_param_depth.decrement();
485    }
486
487    pub fn is_inside_type_param(&self) -> bool {
488        self.current().type_param_depth.is_active()
489    }
490
491    pub fn set_impl_receiver_type(&mut self, ty: Option<Type>) {
492        self.impl_receiver_type = ty;
493    }
494
495    pub fn impl_receiver_type(&self) -> Option<&Type> {
496        self.impl_receiver_type.as_ref()
497    }
498}