mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-28 04:09:05 +00:00
feat: add initializer, destructor syntax
This commit is contained in:
parent
928afaabdd
commit
6b681c5fd1
8 changed files with 135 additions and 6 deletions
|
@ -181,7 +181,8 @@ impl<ASTBuilder: ASTBuildable> GenericHIRBuilder<ASTBuilder> {
|
|||
|
||||
pub fn check(&mut self, ast: AST, mode: &str) -> Result<CompleteArtifact, IncompleteArtifact> {
|
||||
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
|
||||
.check(artifact.object, self.lowerer.module.context.name.clone())
|
||||
.map_err(|(hir, errs)| {
|
||||
|
|
|
@ -3453,11 +3453,20 @@ impl PyCodeGenerator {
|
|||
self.emit_load_const(name);
|
||||
self.emit_store_instr(Identifier::public("__qualname__"), Name);
|
||||
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());
|
||||
if class.need_to_gen_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() {
|
||||
self.emit_simple_block(methods);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,9 @@ use erg_common::traits::{Locational, Stream};
|
|||
use erg_common::Str;
|
||||
use erg_parser::token::TokenKind;
|
||||
|
||||
use crate::context::Context;
|
||||
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};
|
||||
|
||||
#[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 methods that change internal state are not defined in immutable classes
|
||||
#[derive(Debug)]
|
||||
pub struct SideEffectChecker {
|
||||
pub struct SideEffectChecker<'c> {
|
||||
cfg: ErgConfig,
|
||||
path_stack: Vec<Visibility>,
|
||||
block_stack: Vec<BlockKind>,
|
||||
errs: EffectErrors,
|
||||
ctx: &'c Context,
|
||||
}
|
||||
|
||||
impl SideEffectChecker {
|
||||
pub fn new(cfg: ErgConfig) -> Self {
|
||||
impl<'c> SideEffectChecker<'c> {
|
||||
pub fn new(cfg: ErgConfig, ctx: &'c Context) -> Self {
|
||||
Self {
|
||||
cfg,
|
||||
path_stack: vec![],
|
||||
block_stack: vec![],
|
||||
errs: EffectErrors::empty(),
|
||||
ctx,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -402,6 +405,7 @@ impl SideEffectChecker {
|
|||
other => todo!("{other}"),
|
||||
},
|
||||
Expr::Call(call) => {
|
||||
self.constructor_destructor_check(call);
|
||||
self.check_expr(&call.obj);
|
||||
if (call.obj.t().is_procedure()
|
||||
|| 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 {
|
||||
match expr {
|
||||
Expr::Call(call) => {
|
||||
|
|
|
@ -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 {
|
||||
let hint = Some(
|
||||
switch_lang!(
|
||||
|
|
|
@ -642,6 +642,15 @@ impl SubrType {
|
|||
.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 {
|
||||
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> {
|
||||
match self {
|
||||
Self::FreeVar(fv) if fv.is_linked() => {
|
||||
|
|
18
examples/init_del.er
Normal file
18
examples/init_del.er
Normal 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
|
17
tests/should_err/init_del.er
Normal file
17
tests/should_err/init_del.er
Normal 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
|
|
@ -225,6 +225,11 @@ fn exec_infer_trait() -> Result<(), ()> {
|
|||
expect_success("tests/should_ok/infer_trait.er", 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_init_del() -> Result<(), ()> {
|
||||
expect_success("examples/init_del.er", 0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_int() -> Result<(), ()> {
|
||||
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)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_init_del_err() -> Result<(), ()> {
|
||||
expect_failure("tests/should_err/init_del.er", 0, 1)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_invalid_interpol() -> Result<(), ()> {
|
||||
expect_failure("tests/should_err/invalid_interpol.er", 0, 2)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue