1use 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 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(¤t.condition, &Type::Bool);
102 branches.push((condition, self.block(¤t.then_block)));
103 match ¤t.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 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 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
225fn 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
235fn counter() -> LocalInfo {
237 LocalInfo {
238 ty: Type::Int,
239 kind: LocalKind::Counter,
240 }
241}
242
243pub(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}