Skip to main content

nifty_check/checker/
env.rs

1//! What the checker knows while it walks a body: locals in scope, what context it is in,
2//! and which state the code it has seen reads and writes.
3
4use std::collections::{BTreeSet, HashMap};
5
6use crate::hir::{FnId, LocalId, StateSet, Type};
7
8/// What kind of code is being checked, which decides what it may do.
9#[derive(Clone, Debug)]
10pub(super) enum Context {
11    /// A function body; `return` must produce this type.
12    Function { return_type: Type },
13    /// A window or view body: elements, `if` and `for`.
14    Ui,
15    /// A button's action: statements that may change state.
16    Action,
17    /// A state's starting value, which must not read other state.
18    StateInit,
19}
20
21#[derive(Clone, Debug)]
22pub(super) struct LocalInfo {
23    pub ty: Type,
24    pub kind: LocalKind,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub(super) enum LocalKind {
29    /// A parameter, a `let`, or a `for` variable over a computed list.
30    Variable,
31    /// A `for` variable over a place; writing through it changes the original.
32    Alias,
33    /// A `for` loop's index or range counter, which the loop sets each time round.
34    Counter,
35}
36
37/// One function, view or window body: its locals and the scopes that name them.
38#[derive(Debug)]
39pub(super) struct Body {
40    pub locals: Vec<LocalInfo>,
41    scopes: Vec<HashMap<String, LocalId>>,
42    pub context: Context,
43}
44
45impl Body {
46    pub(super) fn new(context: Context) -> Self {
47        Self {
48            locals: Vec::new(),
49            scopes: vec![HashMap::new()],
50            context,
51        }
52    }
53
54    /// Allocates a local and names it in the current scope.
55    pub(super) fn declare(&mut self, name: &str, info: LocalInfo) -> LocalId {
56        let id = self.allocate(info);
57        self.bind(name, id);
58        id
59    }
60
61    /// Allocates a local without naming it yet.
62    pub(super) fn allocate(&mut self, info: LocalInfo) -> LocalId {
63        let id = LocalId(self.locals.len());
64        self.locals.push(info);
65        id
66    }
67
68    pub(super) fn bind(&mut self, name: &str, id: LocalId) {
69        self.scopes
70            .last_mut()
71            .expect("a body always has a scope")
72            .insert(name.to_owned(), id);
73    }
74
75    pub(super) fn lookup(&self, name: &str) -> Option<LocalId> {
76        self.scopes
77            .iter()
78            .rev()
79            .find_map(|scope| scope.get(name).copied())
80    }
81
82    pub(super) fn local(&self, id: LocalId) -> &LocalInfo {
83        &self.locals[id.0]
84    }
85
86    pub(super) fn push_scope(&mut self) {
87        self.scopes.push(HashMap::new());
88    }
89
90    pub(super) fn pop_scope(&mut self) {
91        self.scopes.pop();
92    }
93}
94
95/// State reads and writes seen so far, and the functions called (whose effects count too).
96#[derive(Debug, Default)]
97pub(super) struct Effects {
98    pub reads: StateSet,
99    pub writes: bool,
100    pub calls: BTreeSet<FnId>,
101}
102
103impl Effects {
104    pub(super) fn merge(&mut self, other: &Self) {
105        self.reads.extend(&other.reads);
106        self.writes |= other.writes;
107        self.calls.extend(other.calls.iter().copied());
108    }
109}
110
111/// Every function's reads and writes, including those of the functions it calls.
112#[derive(Debug, Default)]
113pub(super) struct FnEffects {
114    pub reads: Vec<StateSet>,
115    pub writes: Vec<bool>,
116}
117
118impl FnEffects {
119    /// Propagates effects along calls until nothing changes.
120    pub(super) fn solve(direct: &[Effects]) -> Self {
121        let mut reads: Vec<StateSet> = direct.iter().map(|e| e.reads.clone()).collect();
122        let mut writes: Vec<bool> = direct.iter().map(|e| e.writes).collect();
123        let mut changed = true;
124        while changed {
125            changed = false;
126            for (caller, effects) in direct.iter().enumerate() {
127                for callee in &effects.calls {
128                    let callee_reads = reads[callee.0].clone();
129                    let before = reads[caller].clone();
130                    reads[caller].extend(&callee_reads);
131                    let callee_writes = writes[callee.0];
132                    if reads[caller] != before || (callee_writes && !writes[caller]) {
133                        writes[caller] |= callee_writes;
134                        changed = true;
135                    }
136                }
137            }
138        }
139        Self { reads, writes }
140    }
141
142    /// The full reads and whether anything is written, for code with `effects`.
143    pub(super) fn total(&self, effects: &Effects) -> (StateSet, bool) {
144        let mut reads = effects.reads.clone();
145        let mut writes = effects.writes;
146        for callee in &effects.calls {
147            reads.extend(&self.reads[callee.0]);
148            writes |= self.writes[callee.0];
149        }
150        (reads, writes)
151    }
152}