feat: add initializer, destructor syntax

This commit is contained in:
Shunsuke Shibayama 2024-02-10 18:49:04 +09:00
parent 928afaabdd
commit 6b681c5fd1
8 changed files with 135 additions and 6 deletions

View file

@ -181,7 +181,8 @@ impl<ASTBuilder: ASTBuildable> GenericHIRBuilder<ASTBuilder> {
pub fn check(&mut self, ast: AST, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> { pub fn check(&mut self, ast: AST, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> {
let mut artifact = self.lowerer.lower(ast, mode)?; let mut artifact = self.lowerer.lower(ast, mode)?;
let effect_checker = SideEffectChecker::new(self.cfg().clone()); let ctx = &self.lowerer.get_context().unwrap().context;
let effect_checker = SideEffectChecker::new(self.cfg().clone(), ctx);
let hir = effect_checker let hir = effect_checker
.check(artifact.object, self.lowerer.module.context.name.clone()) .check(artifact.object, self.lowerer.module.context.name.clone())
.map_err(|(hir, errs)| { .map_err(|(hir, errs)| {

View file

@ -3453,11 +3453,20 @@ impl PyCodeGenerator {
self.emit_load_const(name); self.emit_load_const(name);
self.emit_store_instr(Identifier::public("__qualname__"), Name); self.emit_store_instr(Identifier::public("__qualname__"), Name);
let mut methods = ClassDef::take_all_methods(class.methods_list); let mut methods = ClassDef::take_all_methods(class.methods_list);
let __init__ = methods.remove_def("__init__"); let __init__ = methods
.remove_def("__init__")
.or_else(|| methods.remove_def("__init__!"));
self.emit_init_method(&class.sig, __init__, class.__new__.clone()); self.emit_init_method(&class.sig, __init__, class.__new__.clone());
if class.need_to_gen_new { if class.need_to_gen_new {
self.emit_new_func(&class.sig, class.__new__); self.emit_new_func(&class.sig, class.__new__);
} }
let __del__ = methods
.remove_def("__del__")
.or_else(|| methods.remove_def("__del__!"));
if let Some(mut __del__) = __del__ {
__del__.sig.ident_mut().vi.py_name = Some(Str::from("__del__"));
self.emit_def(__del__);
}
if !methods.is_empty() { if !methods.is_empty() {
self.emit_simple_block(methods); self.emit_simple_block(methods);
} }

View file

@ -8,8 +8,9 @@ use erg_common::traits::{Locational, Stream};
use erg_common::Str; use erg_common::Str;
use erg_parser::token::TokenKind; use erg_parser::token::TokenKind;
use crate::context::Context;
use crate::error::{EffectError, EffectErrors}; use crate::error::{EffectError, EffectErrors};
use crate::hir::{Array, Def, Dict, Expr, Params, Set, Signature, Tuple, HIR}; use crate::hir::{Array, Call, Def, Dict, Expr, Params, Set, Signature, Tuple, HIR};
use crate::ty::{HasType, Visibility}; use crate::ty::{HasType, Visibility};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
@ -31,20 +32,22 @@ use BlockKind::*;
/// * check if expressions with side effects are not used in functions /// * check if expressions with side effects are not used in functions
/// * check if methods that change internal state are not defined in immutable classes /// * check if methods that change internal state are not defined in immutable classes
#[derive(Debug)] #[derive(Debug)]
pub struct SideEffectChecker { pub struct SideEffectChecker<'c> {
cfg: ErgConfig, cfg: ErgConfig,
path_stack: Vec<Visibility>, path_stack: Vec<Visibility>,
block_stack: Vec<BlockKind>, block_stack: Vec<BlockKind>,
errs: EffectErrors, errs: EffectErrors,
ctx: &'c Context,
} }
impl SideEffectChecker { impl<'c> SideEffectChecker<'c> {
pub fn new(cfg: ErgConfig) -> Self { pub fn new(cfg: ErgConfig, ctx: &'c Context) -> Self {
Self { Self {
cfg, cfg,
path_stack: vec![], path_stack: vec![],
block_stack: vec![], block_stack: vec![],
errs: EffectErrors::empty(), errs: EffectErrors::empty(),
ctx,
} }
} }
@ -402,6 +405,7 @@ impl SideEffectChecker {
other => todo!("{other}"), other => todo!("{other}"),
}, },
Expr::Call(call) => { Expr::Call(call) => {
self.constructor_destructor_check(call);
self.check_expr(&call.obj); self.check_expr(&call.obj);
if (call.obj.t().is_procedure() if (call.obj.t().is_procedure()
|| call || call
@ -486,6 +490,32 @@ impl SideEffectChecker {
} }
} }
fn constructor_destructor_check(&mut self, call: &Call) {
let Some(gen_t) = call.signature_t().and_then(|sig| sig.return_t()) else {
return;
};
// the call generates a new instance
// REVIEW: is this correct?
if !self.in_context_effects_allowed()
&& call
.signature_t()
.is_some_and(|sig| sig.param_ts().iter().all(|p| !p.contains_type(gen_t)))
{
if let Some(typ_ctx) = self.ctx.get_nominal_type_ctx(gen_t) {
if typ_ctx.get_method_kv("__init__!").is_some()
|| typ_ctx.get_method_kv("__del__!").is_some()
{
self.errs.push(EffectError::constructor_destructor_error(
self.cfg.input.clone(),
line!() as usize,
call.loc(),
self.full_path(),
));
}
}
}
}
pub(crate) fn is_impure(expr: &Expr) -> bool { pub(crate) fn is_impure(expr: &Expr) -> bool {
match expr { match expr {
Expr::Call(call) => { Expr::Call(call) => {

View file

@ -401,6 +401,30 @@ impl EffectError {
) )
} }
pub fn constructor_destructor_error(
input: Input,
errno: usize,
loc: Location,
caused_by: String,
) -> Self {
Self::new(
ErrorCore::new(
vec![SubMessage::only_loc(loc)],
switch_lang!(
"japanese" => "このオブジェクトのコンストラクタとデストラクタは副作用があるため,関数内で呼び出すことはできません",
"simplified_chinese" => "此对象的构造函数和析构函数有副作用,因此不能在函数内调用",
"traditional_chinese" => "此對象的構造函數和析構函數有副作用,因此不能在函數內調用",
"english" => "the constructor and destructor of this object have side-effects, so they cannot be called inside a function",
),
errno,
HasEffect,
loc,
),
input,
caused_by,
)
}
pub fn proc_assign_error(input: Input, errno: usize, loc: Location, caused_by: String) -> Self { pub fn proc_assign_error(input: Input, errno: usize, loc: Location, caused_by: String) -> Self {
let hint = Some( let hint = Some(
switch_lang!( switch_lang!(

View file

@ -642,6 +642,15 @@ impl SubrType {
.map(|pt| pt.name().map_or("_", |s| &s[..])) .map(|pt| pt.name().map_or("_", |s| &s[..]))
} }
pub fn param_ts(&self) -> impl Iterator<Item = &Type> + Clone {
self.non_default_params
.iter()
.chain(self.var_params.as_deref())
.chain(self.default_params.iter())
.chain(self.kw_var_params.as_deref())
.map(|pt| pt.typ())
}
pub fn is_no_var(&self) -> bool { pub fn is_no_var(&self) -> bool {
self.var_params.is_none() && self.kw_var_params.is_none() self.var_params.is_none() && self.kw_var_params.is_none()
} }
@ -3325,6 +3334,17 @@ impl Type {
} }
} }
pub fn param_ts(&self) -> Vec<Type> {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().param_ts(),
Self::Refinement(refine) => refine.t.param_ts(),
Self::Subr(subr) => subr.param_ts().cloned().collect(),
Self::Quantified(quant) => quant.param_ts(),
Self::Callable { param_ts, .. } => param_ts.clone(),
_ => vec![],
}
}
pub fn return_t(&self) -> Option<&Type> { pub fn return_t(&self) -> Option<&Type> {
match self { match self {
Self::FreeVar(fv) if fv.is_linked() => { Self::FreeVar(fv) if fv.is_linked() => {

18
examples/init_del.er Normal file
View file

@ -0,0 +1,18 @@
i = !0
C = Class()
C.
__init__! self =
print! "initialize:", self
i.inc!()
__del__! self =
print! "delete:", self
i.dec!()
p!() =
c = C.new()
assert i == 1
print! c
p!()
assert i == 0

View file

@ -0,0 +1,17 @@
i = !0
C = Class()
C.
__init__! self =
print! "initialize:", self
i.inc!()
__del__! self =
print! "delete:", self
i.dec!()
f() =
c = C.new() # ERR
log c
f()
assert i == 0

View file

@ -225,6 +225,11 @@ fn exec_infer_trait() -> Result<(), ()> {
expect_success("tests/should_ok/infer_trait.er", 0) expect_success("tests/should_ok/infer_trait.er", 0)
} }
#[test]
fn exec_init_del() -> Result<(), ()> {
expect_success("examples/init_del.er", 0)
}
#[test] #[test]
fn exec_int() -> Result<(), ()> { fn exec_int() -> Result<(), ()> {
expect_success("tests/should_ok/int.er", 0) expect_success("tests/should_ok/int.er", 0)
@ -561,6 +566,11 @@ fn exec_infer_union_array() -> Result<(), ()> {
expect_failure("tests/should_err/infer_union_array.er", 2, 1) expect_failure("tests/should_err/infer_union_array.er", 2, 1)
} }
#[test]
fn exec_init_del_err() -> Result<(), ()> {
expect_failure("tests/should_err/init_del.er", 0, 1)
}
#[test] #[test]
fn exec_invalid_interpol() -> Result<(), ()> { fn exec_invalid_interpol() -> Result<(), ()> {
expect_failure("tests/should_err/invalid_interpol.er", 0, 2) expect_failure("tests/should_err/invalid_interpol.er", 0, 2)