Skip to main content

nifty_check/checker/
stmt.rs

1//! Statements in function bodies and button actions.
2
3use nifty_syntax::Span;
4use nifty_syntax::ast::{self, AssignOp, ElseBranch, ExprKind as Ast, Ident, IfStmt, StmtKind};
5
6use super::Checker;
7use super::env::{Context, LocalInfo, LocalKind};
8use super::expr::reported;
9use crate::hir::{BinaryOp, Block, ExprKind, Iterable, Loop, PlaceRoot, Stmt, Type};
10
11impl Checker {
12    pub(super) fn block(&mut self, block: &ast::Block) -> Block {
13        self.body.push_scope();
14        let stmts: Vec<Stmt> = block.stmts.iter().filter_map(|s| self.stmt(s)).collect();
15        self.body.pop_scope();
16        stmts.into()
17    }
18
19    fn stmt(&mut self, stmt: &ast::Stmt) -> Option<Stmt> {
20        match &stmt.kind {
21            StmtKind::Let { name, ty, value } => {
22                let annotation = ty.as_ref().map(|ty| self.resolve_type(ty));
23                let value = self.expr(value, annotation.as_ref());
24                let ty = match annotation {
25                    Some(annotation) => {
26                        self.require(&value.ty, &annotation, value.span);
27                        annotation
28                    }
29                    None => value.ty.clone(),
30                };
31                if ty == Type::Void {
32                    self.error("this has no value to store", value.span);
33                }
34                let local = self.body.declare(
35                    &name.name,
36                    LocalInfo {
37                        ty,
38                        kind: LocalKind::Variable,
39                    },
40                );
41                Some(Stmt::Let(local, value))
42            }
43            StmtKind::Assign { target, op, value } => self.assign(target, *op, value),
44            StmtKind::If(if_stmt) => Some(self.if_stmt(if_stmt)),
45            StmtKind::For {
46                index,
47                item,
48                iterable,
49                body,
50            } => {
51                let head = self.loop_head(iterable, index.is_some());
52                let body = self.in_loop(index.as_ref(), item, &head, |checker| checker.block(body));
53                Some(Stmt::For { head, body })
54            }
55            StmtKind::Return(value) => Some(self.return_stmt(value.as_ref(), stmt.span)),
56            StmtKind::Expr(expr) => {
57                let checked = self.expr(expr, None);
58                let is_call = matches!(
59                    checked.kind,
60                    ExprKind::Call(..)
61                        | ExprKind::Push(..)
62                        | ExprKind::Remove(..)
63                        | ExprKind::Download(..)
64                        | ExprKind::DownloadCommand(..)
65                        | ExprKind::Dialog(..)
66                        | ExprKind::Window(..)
67                        | ExprKind::Error
68                );
69                if !is_call {
70                    self.error("this value isn't used", expr.span);
71                }
72                Some(Stmt::Expr(checked))
73            }
74        }
75    }
76
77    fn assign(&mut self, target: &ast::Expr, op: AssignOp, value: &ast::Expr) -> Option<Stmt> {
78        let place = self.place(target)?;
79        self.note_write(&place);
80        let op = match op {
81            AssignOp::Set => None,
82            AssignOp::Add => Some(BinaryOp::Add),
83            AssignOp::Sub => Some(BinaryOp::Sub),
84            AssignOp::Mul => Some(BinaryOp::Mul),
85            AssignOp::Div => Some(BinaryOp::Div),
86        };
87        let value = self.expect(value, &place.ty);
88        // A mismatched value was just reported; checking the operator too would repeat it.
89        if let Some(op) = op
90            && value.ty == place.ty
91        {
92            self.binary_type(op, &place.ty, &value.ty, value.span);
93        }
94        Some(Stmt::Assign { place, op, value })
95    }
96
97    fn if_stmt(&mut self, if_stmt: &IfStmt) -> Stmt {
98        let mut branches = Vec::new();
99        let mut current = if_stmt;
100        loop {
101            let condition = self.expect(&current.condition, &Type::Bool);
102            branches.push((condition, self.block(&current.then_block)));
103            match &current.else_branch {
104                None => {
105                    return Stmt::If {
106                        branches,
107                        otherwise: None,
108                    };
109                }
110                Some(ElseBranch::Block(block)) => {
111                    return Stmt::If {
112                        branches,
113                        otherwise: Some(self.block(block)),
114                    };
115                }
116                Some(ElseBranch::If(next)) => current = next,
117            }
118        }
119    }
120
121    /// Checks what a `for` loops over and allocates its variables. The item aliases each
122    /// element when the list is a place. [`Self::in_loop`] names the variables.
123    pub(super) fn loop_head(&mut self, iterable: &ast::Iterable, has_index: bool) -> Loop {
124        let (iterable, item) = match iterable {
125            ast::Iterable::List(list) => self.list_head(list),
126            ast::Iterable::Range { start, end } => {
127                let start = self.expect(start, &Type::Int);
128                let end = self.expect(end, &Type::Int);
129                (Iterable::Range { start, end }, counter())
130            }
131        };
132        Loop {
133            item: self.body.allocate(item),
134            index: has_index.then(|| self.body.allocate(counter())),
135            iterable,
136        }
137    }
138
139    fn list_head(&mut self, list: &ast::Expr) -> (Iterable, LocalInfo) {
140        let (iterable, list_type, kind) = if is_place_expr(list) {
141            match self.place(list) {
142                Some(place) => {
143                    if let PlaceRoot::State(state) = place.root {
144                        self.effects.reads.insert(state);
145                    }
146                    let ty = place.ty.clone();
147                    (Iterable::Place(place), ty, LocalKind::Alias)
148                }
149                None => (
150                    Iterable::Value(reported(list.span)),
151                    Type::Error,
152                    LocalKind::Variable,
153                ),
154            }
155        } else {
156            let value = self.expr(list, None);
157            let ty = value.ty.clone();
158            (Iterable::Value(value), ty, LocalKind::Variable)
159        };
160        let ty = match &list_type {
161            Type::List(element) => element.as_ref().clone(),
162            Type::Error => Type::Error,
163            other => {
164                self.error(
165                    format!(
166                        "can only loop over a list or a range such as `0..n`, not a `{}`",
167                        self.type_name(other)
168                    ),
169                    list.span,
170                );
171                Type::Error
172            }
173        };
174        (iterable, LocalInfo { ty, kind })
175    }
176
177    /// Runs `check` on a loop's body with the loop's variables named.
178    pub(super) fn in_loop<T>(
179        &mut self,
180        index: Option<&Ident>,
181        item: &Ident,
182        head: &Loop,
183        check: impl FnOnce(&mut Self) -> T,
184    ) -> T {
185        self.body.push_scope();
186        if let (Some(index), Some(local)) = (index, head.index) {
187            if index.name == item.name {
188                self.error(
189                    format!("`{}` names both the index and the item", item.name),
190                    item.span,
191                );
192            }
193            self.body.bind(&index.name, local);
194        }
195        self.body.bind(&item.name, head.item);
196        let checked = check(self);
197        self.body.pop_scope();
198        checked
199    }
200
201    fn return_stmt(&mut self, value: Option<&ast::Expr>, span: Span) -> Stmt {
202        let Context::Function { return_type } = self.body.context.clone() else {
203            self.error("`return` only works inside a function", span);
204            return Stmt::Return(None);
205        };
206        match (value, &return_type) {
207            (None, Type::Void) => Stmt::Return(None),
208            (None, expected) => {
209                self.error(
210                    format!("this function must return a `{}`", self.type_name(expected)),
211                    span,
212                );
213                Stmt::Return(None)
214            }
215            (Some(value), Type::Void) => {
216                let value = self.expr(value, None);
217                self.error("this function doesn't return a value", value.span);
218                Stmt::Return(Some(value))
219            }
220            (Some(value), expected) => Stmt::Return(Some(self.expect(value, expected))),
221        }
222    }
223}
224
225/// Whether the expression names a location (`todos`, `a.b`, `xs[0]`) rather than computing
226/// a new value.
227fn is_place_expr(expr: &ast::Expr) -> bool {
228    match &expr.kind {
229        Ast::Name(_) => true,
230        Ast::Field { base, .. } | Ast::Index { base, .. } => is_place_expr(base),
231        _ => false,
232    }
233}
234
235/// A `for` loop's index or range counter.
236fn counter() -> LocalInfo {
237    LocalInfo {
238        ty: Type::Int,
239        kind: LocalKind::Counter,
240    }
241}
242
243/// Whether every path through `block` ends in `return`.
244pub(super) fn always_returns(block: &[Stmt]) -> bool {
245    match block.last() {
246        Some(Stmt::Return(_)) => true,
247        Some(Stmt::If {
248            branches,
249            otherwise: Some(otherwise),
250        }) => branches.iter().all(|(_, body)| always_returns(body)) && always_returns(otherwise),
251        _ => false,
252    }
253}