use erg_common::log;
use erg_common::traits::Stream;
use erg_common::vis::Visibility;
use erg_common::Str;
use Visibility::*;
use crate::error::{EffectError, EffectErrors, EffectResult};
use crate::hir::{Accessor, Array, Def, Expr, Signature, Tuple, HIR};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum BlockKind {
Func,
ConstFunc,
ConstInstant, Proc,
Instant,
Module,
}
use BlockKind::*;
#[derive(Debug)]
pub struct SideEffectChecker {
path_stack: Vec<(Str, Visibility)>,
block_stack: Vec<BlockKind>,
errs: EffectErrors,
}
impl SideEffectChecker {
pub fn new() -> Self {
Self {
path_stack: vec![],
block_stack: vec![],
errs: EffectErrors::empty(),
}
}
fn full_path(&self) -> String {
self.path_stack
.iter()
.fold(String::new(), |acc, (path, vis)| {
if vis.is_public() {
acc + "." + &path[..]
} else {
acc + "::" + &path[..]
}
})
}
fn in_context_se_allowed(&self) -> bool {
if self.block_stack.len() == 1 {
return true;
}
match (
self.block_stack.get(self.block_stack.len() - 2).unwrap(),
self.block_stack.last().unwrap(),
) {
(_, Func | ConstInstant) => false,
(_, Proc) => true,
(Proc | Module | Instant, Instant) => true,
_ => false,
}
}
pub fn check(mut self, hir: HIR) -> EffectResult<HIR> {
self.path_stack.push((hir.name.clone(), Private));
self.block_stack.push(Module);
log!(info "the side-effects checking process has started.{RESET}");
for expr in hir.module.iter() {
match expr {
Expr::Def(def) => {
self.check_def(def);
}
Expr::ClassDef(type_def) => {
for def in type_def.public_methods.iter() {
self.check_def(def);
}
}
Expr::Call(call) => {
for parg in call.args.pos_args.iter() {
self.check_expr(&parg.expr);
}
for kwarg in call.args.kw_args.iter() {
self.check_expr(&kwarg.expr);
}
}
Expr::BinOp(bin) => {
self.check_expr(&bin.lhs);
self.check_expr(&bin.rhs);
}
Expr::UnaryOp(unary) => {
self.check_expr(&unary.expr);
}
Expr::Accessor(_) | Expr::Lit(_) => {}
other => todo!("{other}"),
}
}
log!(info "the side-effects checking process has completed, found errors: {}{RESET}", self.errs.len());
if self.errs.is_empty() {
Ok(hir)
} else {
Err(self.errs)
}
}
fn check_def(&mut self, def: &Def) {
let name_and_vis = match &def.sig {
Signature::Var(var) => (var.inspect().clone(), var.vis()),
Signature::Subr(subr) => (subr.ident.inspect().clone(), subr.ident.vis()),
};
self.path_stack.push(name_and_vis);
let is_procedural = def.sig.is_procedural();
let is_subr = def.sig.is_subr();
let is_const = def.sig.is_const();
match (is_procedural, is_subr, is_const) {
(true, true, true) => {
panic!("user-defined constant procedures are not allowed");
}
(true, true, false) => {
self.block_stack.push(Proc);
}
(_, false, false) => {
self.block_stack.push(Instant);
}
(false, true, true) => {
self.block_stack.push(ConstFunc);
}
(false, true, false) => {
self.block_stack.push(Func);
}
(_, false, true) => {
self.block_stack.push(ConstInstant);
}
}
for chunk in def.body.block.iter() {
self.check_expr(chunk);
}
self.path_stack.pop();
self.block_stack.pop();
}
fn check_expr(&mut self, expr: &Expr) {
match expr {
Expr::Def(def) => {
self.check_def(def);
}
Expr::ClassDef(type_def) => {
for def in type_def.public_methods.iter() {
self.check_def(def);
}
}
Expr::Array(array) => match array {
Array::Normal(arr) => {
for arg in arr.elems.pos_args.iter() {
self.check_expr(&arg.expr);
}
}
other => todo!("{other}"),
},
Expr::Tuple(tuple) => match tuple {
Tuple::Normal(tup) => {
for arg in tup.elems.pos_args.iter() {
self.check_expr(&arg.expr);
}
}
},
Expr::Record(record) => {
for attr in record.attrs.iter() {
self.check_def(attr);
}
}
Expr::Call(call) => {
if (self.is_procedural(&call.obj)
|| call
.method_name
.as_ref()
.map(|name| name.is_procedural())
.unwrap_or(false))
&& !self.in_context_se_allowed()
{
self.errs.push(EffectError::has_effect(
line!() as usize,
expr,
self.full_path(),
));
}
call.args
.pos_args
.iter()
.for_each(|parg| self.check_expr(&parg.expr));
call.args
.kw_args
.iter()
.for_each(|kwarg| self.check_expr(&kwarg.expr));
}
Expr::UnaryOp(unary) => {
self.check_expr(&unary.expr);
}
Expr::BinOp(bin) => {
self.check_expr(&bin.lhs);
self.check_expr(&bin.rhs);
}
Expr::Lambda(lambda) => {
let is_proc = lambda.is_procedural();
if is_proc {
self.path_stack.push((Str::ever("<lambda!>"), Private));
self.block_stack.push(Proc);
} else {
self.path_stack.push((Str::ever("<lambda>"), Private));
self.block_stack.push(Func);
}
lambda.body.iter().for_each(|chunk| self.check_expr(chunk));
self.path_stack.pop();
self.block_stack.pop();
}
_ => {}
}
}
fn is_procedural(&self, expr: &Expr) -> bool {
match expr {
Expr::Lambda(lambda) => lambda.is_procedural(),
Expr::Call(call) => self.is_procedural(&call.obj),
Expr::Accessor(Accessor::Ident(ident)) => ident.name.is_procedural(),
Expr::Accessor(Accessor::Attr(attr)) => attr.ident.is_procedural(),
Expr::Accessor(_) => todo!(),
_ => false,
}
}
}
impl Default for SideEffectChecker {
fn default() -> Self {
Self::new()
}
}