1use erg_common::config::ErgConfig;
6use erg_common::log;
7use erg_common::traits::{Locational, Stream};
8use erg_common::Str;
9use erg_parser::token::TokenKind;
10
11use crate::context::Context;
12use crate::error::{EffectError, EffectErrors};
13use crate::hir::{Call, Def, Dict, Expr, List, Params, Set, Signature, Tuple, HIR};
14use crate::ty::{HasType, Visibility};
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17enum BlockKind {
18 Func,
20 ConstFunc,
21 ConstInstant, Proc,
24 Instant,
25 Module,
26}
27
28use BlockKind::*;
29
30#[derive(Debug)]
35pub struct SideEffectChecker<'c> {
36 cfg: ErgConfig,
37 path_stack: Vec<Visibility>,
38 block_stack: Vec<BlockKind>,
39 errs: EffectErrors,
40 ctx: &'c Context,
41}
42
43impl<'c> SideEffectChecker<'c> {
44 pub fn new(cfg: ErgConfig, ctx: &'c Context) -> Self {
45 Self {
46 cfg,
47 path_stack: vec![],
48 block_stack: vec![],
49 errs: EffectErrors::empty(),
50 ctx,
51 }
52 }
53
54 fn full_path(&self) -> String {
55 self.path_stack
56 .iter()
57 .fold(String::new(), |acc, vis| {
58 if vis.is_public() {
59 acc + "." + &vis.def_namespace[..]
60 } else {
61 acc + "::" + &vis.def_namespace[..]
62 }
63 })
64 .trim_start_matches(['.', ':'].as_ref())
65 .to_string()
66 }
67
68 fn in_context_effects_allowed(&self) -> bool {
74 if self.block_stack.len() == 1 {
76 return true;
77 }
78 match (
79 self.block_stack.get(self.block_stack.len() - 2).unwrap(),
80 self.block_stack.last().unwrap(),
81 ) {
82 (_, Func | ConstInstant) => false,
83 (_, Proc) => true,
84 (Proc | Module | Instant, Instant) => true,
85 _ => false,
86 }
87 }
88
89 pub fn check(mut self, hir: HIR, namespace: Str) -> Result<HIR, (HIR, EffectErrors)> {
90 self.path_stack.push(Visibility::private(namespace));
91 self.block_stack.push(Module);
92 log!(info "the side-effects checking process has started.{RESET}");
93 for expr in hir.module.iter() {
96 match expr {
97 Expr::Def(def) => {
98 self.check_def(def);
99 }
100 Expr::ClassDef(class_def) => {
101 if let Some(req_sup) = &class_def.require_or_sup {
102 self.check_expr(req_sup);
103 }
104 let name_and_vis = Visibility::new(
105 class_def.sig.vis().clone(),
106 class_def.sig.inspect().clone(),
107 );
108 self.path_stack.push(name_and_vis);
109 for def in class_def.all_methods() {
110 self.check_expr(def);
111 }
112 self.path_stack.pop();
113 }
114 Expr::PatchDef(patch_def) => {
115 self.check_expr(patch_def.base.as_ref());
116 for def in patch_def.methods.iter() {
118 self.check_expr(def);
119 }
120 }
121 Expr::Call(call) => {
122 for parg in call.args.pos_args.iter() {
123 self.check_expr(&parg.expr);
124 }
125 for kwarg in call.args.kw_args.iter() {
126 self.check_expr(&kwarg.expr);
127 }
128 }
129 Expr::BinOp(bin) => {
130 self.check_expr(&bin.lhs);
131 self.check_expr(&bin.rhs);
132 }
133 Expr::UnaryOp(unary) => {
134 self.check_expr(&unary.expr);
135 }
136 Expr::Accessor(_) | Expr::Literal(_) => {}
137 Expr::List(list) => match list {
138 List::Normal(lis) => {
139 for elem in lis.elems.pos_args.iter() {
140 self.check_expr(&elem.expr);
141 }
142 }
143 List::WithLength(lis) => {
144 self.check_expr(&lis.elem);
145 if let Some(len) = &lis.len {
146 self.check_expr(len);
147 }
148 }
149 List::Comprehension(lis) => {
150 self.check_expr(&lis.elem);
151 self.check_expr(&lis.guard);
152 }
153 },
154 Expr::Tuple(tuple) => match tuple {
155 Tuple::Normal(tuple) => {
156 for elem in tuple.elems.pos_args.iter() {
157 self.check_expr(&elem.expr);
158 }
159 }
160 },
161 Expr::Record(rec) => {
162 self.path_stack
163 .push(Visibility::private(Str::ever("<record>")));
164 self.block_stack.push(Instant);
165 for attr in rec.attrs.iter() {
166 self.check_def(attr);
167 }
168 self.path_stack.pop();
169 self.block_stack.pop();
170 }
171 Expr::Set(set) => match set {
172 Set::Normal(set) => {
173 for elem in set.elems.pos_args.iter() {
174 self.check_expr(&elem.expr);
175 }
176 }
177 Set::WithLength(set) => {
178 self.check_expr(&set.elem);
179 self.check_expr(&set.len);
180 }
181 },
182 Expr::Dict(dict) => match dict {
183 Dict::Normal(dict) => {
184 for kv in dict.kvs.iter() {
185 self.check_expr(&kv.key);
186 self.check_expr(&kv.value);
187 }
188 }
189 other => todo!("{other}"),
190 },
191 Expr::TypeAsc(tasc) => {
192 self.check_expr(&tasc.expr);
193 }
194 Expr::Lambda(lambda) => {
195 let is_proc = lambda.is_procedural();
196 if is_proc {
197 self.path_stack
198 .push(Visibility::private(Str::ever("<lambda!>")));
199 self.block_stack.push(Proc);
200 } else {
201 self.path_stack
202 .push(Visibility::private(Str::ever("<lambda>")));
203 self.block_stack.push(Func);
204 }
205 lambda.body.iter().for_each(|chunk| self.check_expr(chunk));
206 self.path_stack.pop();
207 self.block_stack.pop();
208 }
209 Expr::ReDef(_)
210 | Expr::Code(_)
211 | Expr::Compound(_)
212 | Expr::Import(_)
213 | Expr::Dummy(_) => {}
214 }
215 }
216 log!(info "the side-effects checking process has completed, found errors: {}{RESET}", self.errs.len());
217 if self.errs.is_empty() {
218 Ok(hir)
219 } else {
220 Err((hir, self.errs))
221 }
222 }
223
224 fn check_params(&mut self, params: &Params) {
225 for nd_param in params.non_defaults.iter() {
226 if nd_param.vi.t.is_procedure() && !nd_param.inspect().unwrap().ends_with('!') {
227 self.errs.push(EffectError::proc_assign_error(
228 self.cfg.input.clone(),
229 line!() as usize,
230 nd_param.raw.pat.loc(),
231 self.full_path(),
232 ));
233 }
234 }
235 if let Some(var_arg) = params.var_params.as_deref() {
236 if var_arg.vi.t.is_procedure() && !var_arg.inspect().unwrap().ends_with('!') {
237 self.errs.push(EffectError::proc_assign_error(
238 self.cfg.input.clone(),
239 line!() as usize,
240 var_arg.raw.pat.loc(),
241 self.full_path(),
242 ));
243 }
244 }
245 for d_param in params.defaults.iter() {
246 if d_param.sig.vi.t.is_procedure() && !d_param.inspect().unwrap().ends_with('!') {
247 self.errs.push(EffectError::proc_assign_error(
248 self.cfg.input.clone(),
249 line!() as usize,
250 d_param.sig.raw.pat.loc(),
251 self.full_path(),
252 ));
253 }
254 self.check_expr(&d_param.default_val);
255 }
256 }
257
258 fn check_def(&mut self, def: &Def) {
259 let is_procedural = def.sig.is_procedural();
260 let is_subr = def.sig.is_subr();
261 let is_const = def.sig.is_const();
262 if is_subr {
263 let name_and_vis = Visibility::new(def.sig.vis().clone(), def.sig.inspect().clone());
264 self.path_stack.push(name_and_vis);
265 }
266 match (is_procedural, is_subr, is_const) {
267 (true, true, true) => {
268 panic!("user-defined constant procedures are not allowed");
269 }
270 (true, true, false) => {
271 self.block_stack.push(Proc);
272 }
273 (_, false, false) => {
274 self.block_stack.push(Instant);
275 }
276 (false, true, true) => {
277 self.block_stack.push(ConstFunc);
278 }
279 (false, true, false) => {
280 self.block_stack.push(Func);
281 }
282 (_, false, true) => {
283 self.block_stack.push(ConstInstant);
284 }
285 }
286 if let Signature::Subr(sig) = &def.sig {
287 self.check_params(&sig.params);
288 }
289 let last_idx = def.body.block.len().saturating_sub(1);
290 for (i, chunk) in def.body.block.iter().enumerate() {
291 self.check_expr(chunk);
292 if i == last_idx
294 && self.block_stack.last().unwrap() == &Instant
295 && !def.sig.is_procedural()
296 && chunk.t().is_procedure()
297 {
298 self.errs.push(EffectError::proc_assign_error(
299 self.cfg.input.clone(),
300 line!() as usize,
301 def.sig.loc(),
302 self.full_path(),
303 ));
304 }
305 }
306 if is_subr {
307 self.path_stack.pop();
308 }
309 self.block_stack.pop();
310 }
311
312 fn check_expr(&mut self, expr: &Expr) {
334 match expr {
335 Expr::Literal(_) => {}
336 Expr::Def(def) => {
337 self.check_def(def);
338 }
339 Expr::ClassDef(class_def) => {
340 if let Some(req_sup) = &class_def.require_or_sup {
341 self.check_expr(req_sup);
342 }
343 for def in class_def.all_methods() {
344 self.check_expr(def);
345 }
346 }
347 Expr::PatchDef(patch_def) => {
348 self.check_expr(patch_def.base.as_ref());
349 for def in patch_def.methods.iter() {
350 self.check_expr(def);
351 }
352 }
353 Expr::List(list) => match list {
354 List::Normal(lis) => {
355 for elem in lis.elems.pos_args.iter() {
356 self.check_expr(&elem.expr);
357 }
358 }
359 List::WithLength(lis) => {
360 self.check_expr(&lis.elem);
361 if let Some(len) = &lis.len {
362 self.check_expr(len);
363 }
364 }
365 List::Comprehension(lis) => {
366 self.check_expr(&lis.elem);
367 self.check_expr(&lis.guard);
368 }
369 },
370 Expr::Tuple(tuple) => match tuple {
371 Tuple::Normal(tup) => {
372 for arg in tup.elems.pos_args.iter() {
373 self.check_expr(&arg.expr);
374 }
375 }
376 },
377 Expr::Record(record) => {
378 self.path_stack
379 .push(Visibility::private(Str::ever("<record>")));
380 self.block_stack.push(Instant);
381 for attr in record.attrs.iter() {
382 self.check_def(attr);
383 }
384 self.path_stack.pop();
385 self.block_stack.pop();
386 }
387 Expr::Set(set) => match set {
388 Set::Normal(set) => {
389 for elem in set.elems.pos_args.iter() {
390 self.check_expr(&elem.expr);
391 }
392 }
393 Set::WithLength(set) => {
394 self.check_expr(&set.elem);
395 self.check_expr(&set.len);
396 }
397 },
398 Expr::Dict(dict) => match dict {
399 Dict::Normal(dict) => {
400 for kv in dict.kvs.iter() {
401 self.check_expr(&kv.key);
402 self.check_expr(&kv.value);
403 }
404 }
405 other => todo!("{other}"),
406 },
407 Expr::Call(call) => {
408 self.constructor_destructor_check(call);
409 self.check_expr(&call.obj);
410 if (call.obj.t().is_procedure()
411 || call
412 .attr_name
413 .as_ref()
414 .map(|name| name.is_procedural())
415 .unwrap_or(false))
416 && !self.in_context_effects_allowed()
417 {
418 self.errs.push(EffectError::has_effect(
419 self.cfg.input.clone(),
420 line!() as usize,
421 expr,
422 self.full_path(),
423 ));
424 }
425 call.args
426 .pos_args
427 .iter()
428 .for_each(|parg| self.check_expr(&parg.expr));
429 call.args
430 .kw_args
431 .iter()
432 .for_each(|kwarg| self.check_expr(&kwarg.expr));
433 }
434 Expr::UnaryOp(unary) => {
435 self.check_expr(&unary.expr);
436 }
437 Expr::BinOp(bin) => {
438 self.check_expr(&bin.lhs);
439 self.check_expr(&bin.rhs);
440 if (bin.op.kind == TokenKind::IsOp || bin.op.kind == TokenKind::IsNotOp)
441 && !self.in_context_effects_allowed()
442 {
443 self.errs.push(EffectError::has_effect(
444 self.cfg.input.clone(),
445 line!() as usize,
446 expr,
447 self.full_path(),
448 ));
449 }
450 }
451 Expr::Lambda(lambda) => {
452 let is_proc = lambda.is_procedural();
453 if is_proc {
454 self.path_stack
455 .push(Visibility::private(Str::ever("<lambda!>")));
456 self.block_stack.push(Proc);
457 } else {
458 self.path_stack
459 .push(Visibility::private(Str::ever("<lambda>")));
460 self.block_stack.push(Func);
461 }
462 self.check_params(&lambda.params);
463 lambda.body.iter().for_each(|chunk| self.check_expr(chunk));
464 self.path_stack.pop();
465 self.block_stack.pop();
466 }
467 Expr::TypeAsc(type_asc) => {
468 self.check_expr(&type_asc.expr);
469 }
470 Expr::Accessor(acc) => {
471 if !self.in_context_effects_allowed()
472 && !acc.var_info().is_parameter()
473 && acc.ref_t().is_mut_type()
474 && acc.root_obj().map_or(true, |obj| !obj.ref_t().is_ref())
475 && acc.var_info().def_namespace() != &self.full_path()
476 {
477 self.errs.push(EffectError::touch_mut_error(
478 self.cfg.input.clone(),
479 line!() as usize,
480 expr,
481 self.full_path(),
482 ));
483 }
484 }
485 Expr::ReDef(_)
486 | Expr::Code(_)
487 | Expr::Compound(_)
488 | Expr::Import(_)
489 | Expr::Dummy(_) => {}
490 }
491 }
492
493 fn constructor_destructor_check(&mut self, call: &Call) {
494 let Some(gen_t) = call.signature_t().and_then(|sig| sig.return_t()) else {
495 return;
496 };
497 if !self.in_context_effects_allowed()
500 && call
501 .signature_t()
502 .is_some_and(|sig| sig.param_ts().iter().all(|p| !p.contains_type(gen_t)))
503 {
504 if let Some(typ_ctx) = self.ctx.get_nominal_type_ctx(gen_t) {
505 if typ_ctx.get_method_kv("__init__!").is_some()
506 || typ_ctx.get_method_kv("__del__!").is_some()
507 {
508 self.errs.push(EffectError::constructor_destructor_error(
509 self.cfg.input.clone(),
510 line!() as usize,
511 call.loc(),
512 self.full_path(),
513 ));
514 }
515 }
516 }
517 }
518
519 pub(crate) fn is_impure(expr: &Expr) -> bool {
520 match expr {
521 Expr::Call(call) => {
522 call.ref_t().is_procedure()
523 || call
524 .args
525 .pos_args
526 .iter()
527 .any(|parg| Self::is_impure(&parg.expr))
528 || call
529 .args
530 .var_args
531 .iter()
532 .any(|varg| Self::is_impure(&varg.expr))
533 || call
534 .args
535 .kw_args
536 .iter()
537 .any(|kwarg| Self::is_impure(&kwarg.expr))
538 }
539 Expr::BinOp(bin) => Self::is_impure(&bin.lhs) || Self::is_impure(&bin.rhs),
540 Expr::UnaryOp(unary) => Self::is_impure(&unary.expr),
541 Expr::List(lis) => match lis {
542 List::Normal(lis) => lis
543 .elems
544 .pos_args
545 .iter()
546 .any(|elem| Self::is_impure(&elem.expr)),
547 List::WithLength(lis) => {
548 Self::is_impure(&lis.elem)
549 || lis.len.as_ref().is_some_and(|len| Self::is_impure(len))
550 }
551 _ => todo!(),
552 },
553 Expr::Tuple(tup) => match tup {
554 Tuple::Normal(tup) => tup
555 .elems
556 .pos_args
557 .iter()
558 .any(|elem| Self::is_impure(&elem.expr)),
559 },
560 Expr::Set(set) => match set {
561 Set::Normal(set) => set
562 .elems
563 .pos_args
564 .iter()
565 .any(|elem| Self::is_impure(&elem.expr)),
566 Set::WithLength(set) => Self::is_impure(&set.elem) || Self::is_impure(&set.len),
567 },
568 Expr::Dict(dict) => match dict {
569 Dict::Normal(dict) => dict
570 .kvs
571 .iter()
572 .any(|kv| Self::is_impure(&kv.key) || Self::is_impure(&kv.value)),
573 _ => todo!(),
574 },
575 Expr::Lambda(lambda) => {
576 lambda.op.is_procedural() || lambda.body.iter().any(Self::is_impure)
577 }
578 Expr::Def(def) => def.sig.is_procedural() || def.body.block.iter().any(Self::is_impure),
579 Expr::Code(block) | Expr::Compound(block) => block.iter().any(Self::is_impure),
587 _ => false,
588 }
589 }
590
591 pub(crate) fn is_pure(expr: &Expr) -> bool {
592 !Self::is_impure(expr)
593 }
594}