From b755ceb274614d38f56ff43cc08e084fd2866654 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Sat, 24 Dec 2022 11:57:12 +0900 Subject: [PATCH] Implement some errors --- crates/py2erg/convert.rs | 66 +++++++++++++++++++++++++++++----------- crates/py2erg/error.rs | 46 ++++++++++++++++++++++++++++ docs/source/errors.md | 14 +++++++++ src/analyze.rs | 11 +++++-- tests/class.py | 6 ++-- 5 files changed, 120 insertions(+), 23 deletions(-) diff --git a/crates/py2erg/convert.rs b/crates/py2erg/convert.rs index 7aedcf3..246e9e8 100644 --- a/crates/py2erg/convert.rs +++ b/crates/py2erg/convert.rs @@ -1,7 +1,7 @@ use erg_common::config::ErgConfig; use erg_common::fresh::fresh_varname; use erg_common::traits::{Locational, Stream}; -use erg_compiler::artifact::CompleteArtifact; +use erg_compiler::artifact::{IncompleteArtifact}; use rustpython_parser::ast::{StatementType, ExpressionType, Located, Program, Number, StringGroup, Operator, BooleanOperator, UnaryOperator, Suite, Parameters, Parameter, Comparison}; use rustpython_parser::ast::Location as PyLocation; @@ -181,6 +181,7 @@ pub struct ASTConverter { /// Erg does not allow variables to be defined multiple times, so rename them using this names: HashMap, warns: CompileErrors, + errs: CompileErrors, } impl ASTConverter { @@ -191,6 +192,7 @@ impl ASTConverter { namespace: vec![String::from("")], names: HashMap::new(), warns: CompileErrors::empty(), + errs: CompileErrors::empty(), } } @@ -523,35 +525,63 @@ impl ASTConverter { Block::new(new_block) } - // def __init__(self, x: Int, y: Int): + fn find_init_self(&mut self, init_def: &Def) { + match &init_def.sig { + Signature::Subr(subr) => { + if let Some(first) = subr.params.non_defaults.get(0) { + if first.inspect().map(|s| &s[..]) == Some("self") { + return; + } + } + self.errs.push(self_not_found_error( + self.cfg.input.clone(), + subr.loc(), + self.namespace.join("."), + )); + } + Signature::Var(var) => { + self.errs.push(init_var_error( + self.cfg.input.clone(), + var.loc(), + self.namespace.join("."), + )); + } + } + } + + // def __init__(self, x: Int, y: Int, z): // self.x = x // self.y = y + // self.z = z // ↓ - // {x: Int, y: Int}, .__call__(x: Int, y: Int): Self = .unreachable() - fn extract_init(&self, def: Def) -> (Expr, Def) { - let l_brace = Token::new(TokenKind::LBrace, "{", def.ln_begin().unwrap_or(0), def.col_begin().unwrap_or(0)); - let r_brace = Token::new(TokenKind::RBrace, "}", def.ln_end().unwrap_or(0), def.col_end().unwrap_or(0)); - let Signature::Subr(sig) = def.sig else { unreachable!() }; + // {x: Int, y: Int, z: Never}, .__call__(x: Int, y: Int, z: Obj): Self = .unreachable() + fn extract_init(&mut self, init_def: Def) -> (Expr, Def) { + self.find_init_self(&init_def); + let l_brace = Token::new(TokenKind::LBrace, "{", init_def.ln_begin().unwrap_or(0), init_def.col_begin().unwrap_or(0)); + let r_brace = Token::new(TokenKind::RBrace, "}", init_def.ln_end().unwrap_or(0), init_def.col_end().unwrap_or(0)); + let Signature::Subr(sig) = init_def.sig else { unreachable!() }; let mut fields = vec![]; let mut params = vec![]; - for chunk in def.body.block { + for chunk in init_def.body.block { #[allow(clippy::single_match)] match chunk { Expr::AttrDef(adef) => { let Accessor::Attr(attr) = adef.attr else { break; }; if attr.obj.get_name().map(|s| &s[..]) == Some("self") { - let typ_name = if let Some(t_spec_op) = sig.params.non_defaults.iter() + let (param_typ_name, arg_typ_name) = if let Some(t_spec_op) = sig.params.non_defaults.iter() .find(|¶m| param.inspect() == Some(attr.ident.inspect())) .and_then(|param| param.t_spec.as_ref()) { - t_spec_op.t_spec.to_string().replace('.', "") + let typ_name = t_spec_op.t_spec.to_string().replace('.', ""); + (typ_name.clone(), typ_name) } else { - "Never".to_string() + ("Obj".to_string(), "Never".to_string()) // accept any type, can be any type }; - let typ_ident = Identifier::public_with_line(DOT, typ_name.into(), attr.obj.ln_begin().unwrap_or(0)); - let typ_spec = TypeSpec::PreDeclTy(PreDeclTypeSpec::Simple(SimpleTypeSpec::new(typ_ident.clone(), ConstArgs::empty()))); - let typ_spec = TypeSpecWithOp::new(COLON, typ_spec); - params.push(NonDefaultParamSignature::new(ParamPattern::VarName(attr.ident.name.clone()), Some(typ_spec))); - let typ = Expr::Accessor(Accessor::Ident(typ_ident)); + let param_typ_ident = Identifier::public_with_line(DOT, param_typ_name.into(), attr.obj.ln_begin().unwrap_or(0)); + let param_typ_spec = TypeSpec::PreDeclTy(PreDeclTypeSpec::Simple(SimpleTypeSpec::new(param_typ_ident.clone(), ConstArgs::empty()))); + let param_typ_spec = TypeSpecWithOp::new(COLON, param_typ_spec); + let arg_typ_ident = Identifier::public_with_line(DOT, arg_typ_name.into(), attr.obj.ln_begin().unwrap_or(0)); + params.push(NonDefaultParamSignature::new(ParamPattern::VarName(attr.ident.name.clone()), Some(param_typ_spec))); + let typ = Expr::Accessor(Accessor::Ident(arg_typ_ident)); let sig = Signature::Var(VarSignature::new(VarPattern::Ident(attr.ident), None)); let body = DefBody::new(EQUAL, Block::new(vec![typ]), DefId(0)); let field_type_def = Def::new(sig, body); @@ -964,10 +994,10 @@ impl ASTConverter { } } - pub fn convert_program(mut self, program: Program) -> CompleteArtifact { + pub fn convert_program(mut self, program: Program) -> IncompleteArtifact { let program = program.statements.into_iter() .map(|stmt| self.convert_statement(stmt, true)) .collect(); - CompleteArtifact::new(Module::new(program), self.warns) + IncompleteArtifact::new(Some(Module::new(program)), self.errs, self.warns) } } diff --git a/crates/py2erg/error.rs b/crates/py2erg/error.rs index 6c6bb9a..f883f97 100644 --- a/crates/py2erg/error.rs +++ b/crates/py2erg/error.rs @@ -26,3 +26,49 @@ pub(crate) fn reassign_func_error( caused_by, ) } + +pub(crate) fn self_not_found_error( + input: Input, + loc: Location, + caused_by: String, +) -> CompileError { + CompileError::new( + ErrorCore::new( + vec![SubMessage::only_loc(loc)], + switch_lang!( + "japanese" => format!("このメソッドは第一引数にselfを取るべきですが、見つかりませんでした"), + "simplified_chinese" => format!("该方法应该有第一个参数self,但是没有找到"), + "traditional_chinese" => format!("該方法應該有第一個參數self,但是沒有找到"), + "english" => format!("This method should have the first parameter `self`, but it was not found"), + ), + 2, + ErrorKind::NameError, + loc, + ), + input, + caused_by, + ) +} + +pub(crate) fn init_var_error( + input: Input, + loc: Location, + caused_by: String, +) -> CompileError { + CompileError::new( + ErrorCore::new( + vec![SubMessage::only_loc(loc)], + switch_lang!( + "japanese" => format!("__init__はメソッドです。メンバ変数として宣言することはできません"), + "simplified_chinese" => format!("__init__是方法。不能声明为变量"), + "traditional_chinese" => format!("__init__是方法。不能宣告為變量"), + "english" => format!("__init__ is a method. It cannot be declared as a member variable"), + ), + 3, + ErrorKind::NameError, + loc, + ), + input, + caused_by, + ) +} diff --git a/docs/source/errors.md b/docs/source/errors.md index c1cbc31..dc31b58 100644 --- a/docs/source/errors.md +++ b/docs/source/errors.md @@ -10,3 +10,17 @@ def f(): return "a" # E0001: Reassignment of a function referenced by other func print(g()) ``` + +## E0002: `__init__` doesn't have a first parameter named `self` + +```python +class C: + def __init__(a): pass # E0002 +``` + +## E0003: `__init__` as a member variable + +```python +class C: + __init__ = 1 # E0003 +``` diff --git a/src/analyze.rs b/src/analyze.rs index c5df795..a19d2c8 100644 --- a/src/analyze.rs +++ b/src/analyze.rs @@ -87,17 +87,22 @@ impl PythonAnalyzer { IncompleteArtifact::new(None, CompileErrors::from(err), CompileErrors::empty()) })?; let converter = py2erg::ASTConverter::new(self.cfg.copy(), ShadowingMode::Invisible); - let CompleteArtifact{ object: erg_module, mut warns } = converter.convert_program(py_program); + let IncompleteArtifact{ object: Some(erg_module), mut errors, mut warns } = converter.convert_program(py_program) else { unreachable!() }; let erg_ast = AST::new(erg_common::Str::rc(filename), erg_module); erg_common::log!("AST: {erg_ast}"); match self.checker.lower(erg_ast, mode) { Ok(mut artifact) => { artifact.warns.extend(warns); artifact.warns = handle_err::filter_errors(self.checker.get_mod_ctx(), artifact.warns); - Ok(artifact) + if errors.is_empty() { + Ok(artifact) + } else { + Err(IncompleteArtifact::new(Some(artifact.object), errors, artifact.warns)) + } } Err(iart) => { - let errors = handle_err::filter_errors(self.checker.get_mod_ctx(), iart.errors); + errors.extend(iart.errors); + let errors = handle_err::filter_errors(self.checker.get_mod_ctx(), errors); warns.extend(iart.warns); let warns = handle_err::filter_errors(self.checker.get_mod_ctx(), warns); Err(IncompleteArtifact::new(iart.object, errors, warns)) diff --git a/tests/class.py b/tests/class.py index dd06d75..22cac60 100644 --- a/tests/class.py +++ b/tests/class.py @@ -8,13 +8,15 @@ class x(): pass y = x() class C: - def __init__(x: int): + def __init__(self, x: int, y): # y: Obj self.x = x + self.y = y # y: Never def method(self): return self.x -c = C(1) +c = C(1, 2) assert c.x == 1 +assert c.y == 2 # OK, c.y == "a" is also OK a = c.method() # OK _: int = a + 1 b = C("a").method() # ERR