1use crate::ast::{Expr, Function, Stmt};
5use crate::errors::Result;
6use std::collections::HashSet;
7
8#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub enum Effect {
11 IO,
13 Memory,
15 Panic,
17 Async,
19 Unsafe,
21 Pure,
23}
24
25#[derive(Debug, Clone, PartialEq, Eq, Default)]
27pub struct EffectSet {
28 effects: HashSet<Effect>,
29}
30
31impl EffectSet {
32 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub fn singleton(effect: Effect) -> Self {
39 let mut effects = HashSet::new();
40 effects.insert(effect);
41 Self { effects }
42 }
43
44 pub fn add(&mut self, effect: Effect) {
46 if effect != Effect::Pure {
48 self.effects.remove(&Effect::Pure);
49 }
50 self.effects.insert(effect);
51 }
52
53 pub fn union(&mut self, other: &EffectSet) {
55 for effect in &other.effects {
56 self.add(effect.clone());
57 }
58 }
59
60 pub fn is_pure(&self) -> bool {
62 self.effects.is_empty() || self.effects.contains(&Effect::Pure)
63 }
64
65 pub fn contains(&self, effect: &Effect) -> bool {
67 self.effects.contains(effect)
68 }
69
70 pub fn effects(&self) -> &HashSet<Effect> {
72 &self.effects
73 }
74}
75
76pub struct EffectAnalyzer {
78 function_effects: std::collections::HashMap<String, EffectSet>,
80 builtin_effects: std::collections::HashMap<String, EffectSet>,
82}
83
84impl Default for EffectAnalyzer {
85 fn default() -> Self {
86 let mut builtin_effects = std::collections::HashMap::new();
87
88 builtin_effects.insert("print".to_string(), EffectSet::singleton(Effect::IO));
90 builtin_effects.insert("print_int".to_string(), EffectSet::singleton(Effect::IO));
91 builtin_effects.insert("file_open".to_string(), EffectSet::singleton(Effect::IO));
92 builtin_effects.insert(
93 "file_read_all".to_string(),
94 EffectSet::singleton(Effect::IO),
95 );
96 builtin_effects.insert(
97 "file_read_line".to_string(),
98 EffectSet::singleton(Effect::IO),
99 );
100 builtin_effects.insert("file_write".to_string(), EffectSet::singleton(Effect::IO));
101 builtin_effects.insert("file_close".to_string(), EffectSet::singleton(Effect::IO));
102 builtin_effects.insert("file_exists".to_string(), EffectSet::singleton(Effect::IO));
103
104 builtin_effects.insert("string_len".to_string(), EffectSet::new());
109 builtin_effects.insert("string_concat".to_string(), EffectSet::new());
110 builtin_effects.insert("string_eq".to_string(), EffectSet::new());
111 builtin_effects.insert("string_char_at".to_string(), EffectSet::new());
112 builtin_effects.insert("string_substring".to_string(), EffectSet::new());
113 builtin_effects.insert("string_from_char".to_string(), EffectSet::new());
114 builtin_effects.insert("char_is_digit".to_string(), EffectSet::new());
115 builtin_effects.insert("char_is_alpha".to_string(), EffectSet::new());
116 builtin_effects.insert("char_is_whitespace".to_string(), EffectSet::new());
117 builtin_effects.insert("string_to_int".to_string(), EffectSet::new());
118 builtin_effects.insert("int_to_string".to_string(), EffectSet::new());
119
120 Self {
121 function_effects: std::collections::HashMap::new(),
122 builtin_effects,
123 }
124 }
125}
126
127impl EffectAnalyzer {
128 pub fn new() -> Self {
129 Self::default()
130 }
131
132 pub fn analyze_function(&mut self, func: &Function) -> Result<EffectSet> {
134 let mut effects = EffectSet::new();
135
136 if func.is_async {
138 effects.add(Effect::Async);
139 }
140
141 for stmt in &func.body {
143 let stmt_effects = self.analyze_statement(stmt)?;
144 effects.union(&stmt_effects);
145 }
146
147 self.function_effects
149 .insert(func.name.clone(), effects.clone());
150
151 Ok(effects)
152 }
153
154 fn analyze_statement(&mut self, stmt: &Stmt) -> Result<EffectSet> {
156 match stmt {
157 Stmt::Expr(expr) => self.analyze_expression(expr),
158 Stmt::Let { value, .. } => self.analyze_expression(value),
159 Stmt::Return(Some(expr)) => self.analyze_expression(expr),
160 Stmt::Return(None) => Ok(EffectSet::new()),
161 Stmt::If {
162 condition,
163 then_branch,
164 else_branch,
165 ..
166 } => {
167 let mut effects = self.analyze_expression(condition)?;
168
169 for stmt in then_branch {
170 let stmt_effects = self.analyze_statement(stmt)?;
171 effects.union(&stmt_effects);
172 }
173
174 if let Some(else_stmts) = else_branch {
175 for stmt in else_stmts {
176 let stmt_effects = self.analyze_statement(stmt)?;
177 effects.union(&stmt_effects);
178 }
179 }
180
181 Ok(effects)
182 }
183 Stmt::While {
184 condition, body, ..
185 } => {
186 let mut effects = self.analyze_expression(condition)?;
187
188 for stmt in body {
189 let stmt_effects = self.analyze_statement(stmt)?;
190 effects.union(&stmt_effects);
191 }
192
193 Ok(effects)
194 }
195 Stmt::For {
196 var: _, iter, body, ..
197 } => {
198 let mut effects = self.analyze_expression(iter)?;
199
200 for stmt in body {
201 let stmt_effects = self.analyze_statement(stmt)?;
202 effects.union(&stmt_effects);
203 }
204
205 Ok(effects)
206 }
207 Stmt::Match { expr, arms, .. } => {
208 let mut effects = self.analyze_expression(expr)?;
209
210 for arm in arms {
211 for stmt in &arm.body {
214 let stmt_effects = self.analyze_statement(stmt)?;
215 effects.union(&stmt_effects);
216 }
217 }
218
219 Ok(effects)
220 }
221 Stmt::Break { .. } | Stmt::Continue { .. } => Ok(EffectSet::new()),
222 Stmt::Unsafe { body, .. } => {
223 let mut effects = EffectSet::singleton(Effect::Unsafe);
224
225 for stmt in body {
226 let stmt_effects = self.analyze_statement(stmt)?;
227 effects.union(&stmt_effects);
228 }
229
230 Ok(effects)
231 }
232 Stmt::Assign { value, .. } => self.analyze_expression(value),
233 }
234 }
235
236 fn analyze_expression(&mut self, expr: &Expr) -> Result<EffectSet> {
238 match expr {
239 Expr::Integer(_) | Expr::String(_) | Expr::Bool(_) | Expr::Ident(_) => {
241 Ok(EffectSet::new())
242 }
243
244 Expr::Call { func, args, .. } => {
246 let mut effects = EffectSet::new();
247
248 let func_effects = self.analyze_expression(func)?;
250 effects.union(&func_effects);
251
252 for arg in args {
254 let arg_effects = self.analyze_expression(arg)?;
255 effects.union(&arg_effects);
256 }
257
258 if let Expr::Ident(func_name) = func.as_ref() {
260 if let Some(builtin_effects) = self.builtin_effects.get(func_name) {
261 effects.union(builtin_effects);
262 } else if let Some(func_effects) = self.function_effects.get(func_name) {
263 effects.union(func_effects);
264 }
265 }
267
268 Ok(effects)
269 }
270
271 Expr::Binary { left, right, .. } => {
273 let mut effects = self.analyze_expression(left)?;
274 let right_effects = self.analyze_expression(right)?;
275 effects.union(&right_effects);
276 Ok(effects)
277 }
278
279 Expr::Unary { operand, .. } => self.analyze_expression(operand),
281
282 Expr::ArrayLiteral { elements, .. } => {
284 let mut effects = EffectSet::new();
285 for elem in elements {
286 let elem_effects = self.analyze_expression(elem)?;
287 effects.union(&elem_effects);
288 }
289 Ok(effects)
290 }
291
292 Expr::ArrayRepeat { value, count, .. } => {
293 let mut effects = self.analyze_expression(value)?;
294 let count_effects = self.analyze_expression(count)?;
295 effects.union(&count_effects);
296 Ok(effects)
297 }
298
299 Expr::Index { array, index, .. } => {
300 let mut effects = self.analyze_expression(array)?;
301 let index_effects = self.analyze_expression(index)?;
302 effects.union(&index_effects);
303 Ok(effects)
304 }
305
306 Expr::StructLiteral { fields, .. } => {
308 let mut effects = EffectSet::new();
309 for (_, field_expr) in fields {
310 let field_effects = self.analyze_expression(field_expr)?;
311 effects.union(&field_effects);
312 }
313 Ok(effects)
314 }
315
316 Expr::FieldAccess { object, .. } => self.analyze_expression(object),
317
318 Expr::EnumConstructor { data, .. } => {
320 let mut effects = EffectSet::new();
321 if let Some(constructor_data) = data {
322 match constructor_data {
323 crate::ast::EnumConstructorData::Tuple(exprs) => {
324 for expr in exprs {
325 let expr_effects = self.analyze_expression(expr)?;
326 effects.union(&expr_effects);
327 }
328 }
329 crate::ast::EnumConstructorData::Struct(fields) => {
330 for (_, expr) in fields {
331 let expr_effects = self.analyze_expression(expr)?;
332 effects.union(&expr_effects);
333 }
334 }
335 }
336 }
337 Ok(effects)
338 }
339
340 Expr::Range { start, end, .. } => {
342 let mut effects = EffectSet::new();
343 let start_effects = self.analyze_expression(start)?;
344 effects.union(&start_effects);
345 let end_effects = self.analyze_expression(end)?;
346 effects.union(&end_effects);
347 Ok(effects)
348 }
349
350 Expr::Reference { expr, .. } => self.analyze_expression(expr),
352 Expr::Deref { expr, .. } => self.analyze_expression(expr),
353
354 Expr::Question { expr, .. } => {
356 let mut effects = self.analyze_expression(expr)?;
357 effects.add(Effect::Panic);
358 Ok(effects)
359 }
360
361 Expr::Await { expr, .. } => {
363 let mut effects = self.analyze_expression(expr)?;
364 effects.add(Effect::Async);
365 Ok(effects)
366 }
367
368 Expr::MacroInvocation { .. } => {
370 Ok(EffectSet::new())
373 }
374 }
375 }
376
377 pub fn get_function_effects(&self, func_name: &str) -> Option<&EffectSet> {
379 self.function_effects.get(func_name)
380 }
381
382 pub fn is_function_pure(&self, func_name: &str) -> bool {
384 self.function_effects
385 .get(func_name)
386 .map(|effects| effects.is_pure())
387 .unwrap_or(true) }
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn test_effect_set() {
397 let mut effects = EffectSet::new();
398 assert!(effects.is_pure());
399
400 effects.add(Effect::IO);
401 assert!(!effects.is_pure());
402 assert!(effects.contains(&Effect::IO));
403
404 let other = EffectSet::singleton(Effect::Async);
405 effects.union(&other);
406 assert!(effects.contains(&Effect::IO));
407 assert!(effects.contains(&Effect::Async));
408 }
409}