Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add constructor & destructor syntax #488

Merged
merged 13 commits into from
Feb 11, 2024
38 changes: 30 additions & 8 deletions crates/erg_common/io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -499,15 +499,15 @@ impl Input {

pub fn resolve_path(&self, path: &Path, cfg: &ErgConfig) -> Option<PathBuf> {
self.resolve_real_path(path, cfg)
.or_else(|| self.resolve_decl_path(path))
.or_else(|| self.resolve_decl_path(path, cfg))
}

/// resolution order:
/// 1. `./{path/to}.er`
/// 2. `./{path/to}/__init__.er`
/// 3. `std/{path/to}.er`
/// 4. `std/{path/to}/__init__.er`
/// 5. `pkgs/{path/to}/lib.er`
/// 5. `pkgs/{path/to}/src/lib.er`
pub fn resolve_real_path(&self, path: &Path, cfg: &ErgConfig) -> Option<PathBuf> {
if let Ok(path) = self.resolve_local(path) {
Some(path)
Expand All @@ -522,7 +522,7 @@ impl Input {
.canonicalize()
{
Some(normalize_path(path))
} else if let Some(pkg) = self.resolve_project_dep_path(path, cfg) {
} else if let Some(pkg) = self.resolve_project_dep_path(path, cfg, false) {
Some(normalize_path(pkg))
} else if path == Path::new("unsound") {
Some(PathBuf::from("unsound"))
Expand All @@ -531,15 +531,25 @@ impl Input {
}
}

fn resolve_project_dep_path(&self, path: &Path, cfg: &ErgConfig) -> Option<PathBuf> {
fn resolve_project_dep_path(
&self,
path: &Path,
cfg: &ErgConfig,
decl: bool,
) -> Option<PathBuf> {
let name = format!("{}", path.display());
let pkg = cfg.packages.iter().find(|p| p.as_name == name)?;
let path = if let Some(path) = pkg.path {
PathBuf::from(path).canonicalize().ok()?
} else {
erg_pkgs_path().join(pkg.name).join(pkg.version)
};
Some(path.join("src").join("lib.er"))
let path = if decl {
path.join("src").join("lib.d.er")
} else {
path.join("src").join("lib.er")
};
path.canonicalize().ok()
}

/// resolution order:
Expand All @@ -552,9 +562,10 @@ impl Input {
/// (and repeat for the project root)
/// 7. `std/{path/to}.d.er`
/// 8. `std/{path/to}/__init__.d.er`
/// 9. `site-packages/{path}/__pycache__/{to}.d.er`
/// 10. `site-packages/{path/to}/__pycache__/__init__.d.er`
pub fn resolve_decl_path(&self, path: &Path) -> Option<PathBuf> {
/// 9. `pkgs/{path/to}/src/lib.d.er`
/// 10. `site-packages/{path}/__pycache__/{to}.d.er`
/// 11. `site-packages/{path/to}/__pycache__/__init__.d.er`
pub fn resolve_decl_path(&self, path: &Path, cfg: &ErgConfig) -> Option<PathBuf> {
if let Ok(path) = self.resolve_local_decl(self.dir(), path) {
return Some(path);
}
Expand All @@ -579,6 +590,9 @@ impl Input {
return Some(path);
}
}
if let Some(pkg) = self.resolve_project_dep_path(path, cfg, true) {
return Some(normalize_path(pkg));
}
for site_packages in python_site_packages() {
if let Some(path) = Self::resolve_site_pkgs_decl_path(site_packages, path) {
return Some(path);
Expand Down Expand Up @@ -668,6 +682,14 @@ impl Input {
py_path.push(last);
decl_path == py_path
}

pub fn mode(&self) -> &'static str {
if self.path().to_string_lossy().ends_with(".d.er") {
"declare"
} else {
"exec"
}
}
}

#[derive(Debug)]
Expand Down
3 changes: 2 additions & 1 deletion crates/erg_compiler/build_hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)| {
Expand Down
16 changes: 11 additions & 5 deletions crates/erg_compiler/build_package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use std::time::{Duration, SystemTime};
use erg_common::config::ErgMode;

use erg_common::config::ErgConfig;
use erg_common::consts::ELS;
use erg_common::consts::{DEBUG_MODE, ELS};
use erg_common::debug_power_assert;
use erg_common::dict::Dict;
use erg_common::env::is_std_decl_path;
Expand Down Expand Up @@ -228,7 +228,9 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: BuildRunnable> Runnable

fn exec(&mut self) -> Result<ExitStatus, Self::Errs> {
let src = self.cfg_mut().input.read();
let artifact = self.build(src, "exec").map_err(|arti| arti.errors)?;
let artifact = self
.build(src, self.cfg.input.mode())
.map_err(|arti| arti.errors)?;
artifact.warns.write_all_stderr();
println!("{}", artifact.object);
Ok(ExitStatus::compile_passed(artifact.warns.len()))
Expand Down Expand Up @@ -382,7 +384,7 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable>
));
}
};
self.build_root(ast, "exec")
self.build_root(ast, self.cfg.input.mode())
}

pub fn build_root(
Expand Down Expand Up @@ -525,7 +527,11 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable>
.spawn()
.and_then(|mut child| child.wait())
{
if let Some(path) = self.cfg.input.resolve_decl_path(Path::new(&__name__[..])) {
if let Some(path) = self
.cfg
.input
.resolve_decl_path(Path::new(&__name__[..]), &self.cfg)
{
let size = metadata(&path).unwrap().len();
// if pylyzer crashed
if !status.success() && size == 0 {
Expand Down Expand Up @@ -728,7 +734,7 @@ impl<ASTBuilder: ASTBuildable, HIRBuilder: Buildable>
// return;
} else if let Some(inliner) = self.inlines.get(path).cloned() {
self.build_deps_and_module(&inliner, graph);
} else {
} else if DEBUG_MODE {
todo!("{path} is not found in self.inlines and self.asts");
}
}
Expand Down
11 changes: 10 additions & 1 deletion crates/erg_compiler/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3495,11 +3495,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);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/erg_compiler/context/initialize/const_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1445,7 +1445,7 @@ pub(crate) fn resolve_decl_path_func(
return Err(type_mismatch("Str", other, "Path"));
}
};
let Some(path) = ctx.cfg.input.resolve_decl_path(path) else {
let Some(path) = ctx.cfg.input.resolve_decl_path(path, &ctx.cfg) else {
return Err(ErrorCore::new(
vec![SubMessage::only_loc(Location::Unknown)],
format!("Path {} is not found", path.display()),
Expand Down
6 changes: 5 additions & 1 deletion crates/erg_compiler/context/register.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2267,7 +2267,11 @@ impl Context {
}

fn get_decl_path(&self, __name__: &Str, loc: &impl Locational) -> CompileResult<PathBuf> {
match self.cfg.input.resolve_decl_path(Path::new(&__name__[..])) {
match self
.cfg
.input
.resolve_decl_path(Path::new(&__name__[..]), &self.cfg)
{
Some(path) => {
if self.cfg.input.decl_file_is(&path) {
return Ok(path);
Expand Down
38 changes: 34 additions & 4 deletions crates/erg_compiler/effectcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand All @@ -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,
}
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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) => {
Expand Down
24 changes: 24 additions & 0 deletions crates/erg_compiler/error/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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!(
Expand Down
5 changes: 5 additions & 0 deletions crates/erg_compiler/lib/std/semver.er
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
if self.major == 0, do:
self.compatible_with::return self == other
self.major == other.major
is_valid s: Str =
match s.split("."):
[major, minor, patch] -> major.isnumeric() and minor.isnumeric() and patch.isnumeric()
[major, minor, patch, pre] -> major.isnumeric() and minor.isnumeric() and patch.isnumeric() and pre.isalnum()
_ -> False
#[
greater self, other: .Version =
match [self.major > other.major, self.major >= other.major, self.minor > other.minor, self.minor >= other.minor, self.patch > other.patch]:
Expand Down
2 changes: 1 addition & 1 deletion crates/erg_compiler/link_hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ impl<'a> HIRLinker<'a> {
let mod_path = self
.cfg
.input
.resolve_decl_path(Path::new(&mod_name_str[..]))
.resolve_decl_path(Path::new(&mod_name_str[..]), self.cfg)
.unwrap();
if !mod_path
.canonicalize()
Expand Down
20 changes: 20 additions & 0 deletions crates/erg_compiler/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}
Expand Down Expand Up @@ -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() => {
Expand Down
18 changes: 18 additions & 0 deletions examples/init_del.er
Original file line number Diff line number Diff line change
@@ -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 changes: 17 additions & 0 deletions tests/should_err/init_del.er
Original file line number Diff line number Diff line change
@@ -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
Loading
Loading