From 2a98535d4cb637f8c48c9e5ffb8b38558b5b5915 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Fri, 27 Dec 2024 15:46:59 +0900 Subject: [PATCH] feat: ABC implementation check --- Cargo.lock | 20 +++++------ Cargo.toml | 6 ++-- README.md | 8 +++-- crates/py2erg/convert.rs | 74 ++++++++++++++++++++++++++-------------- tests/abc.py | 19 +++++++++++ tests/test.rs | 5 +++ 6 files changed, 91 insertions(+), 41 deletions(-) create mode 100644 tests/abc.py diff --git a/Cargo.lock b/Cargo.lock index c8caef5..6fe2910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,9 +150,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "els" -version = "0.1.61" +version = "0.1.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be76108fd7329b2130fcc168b00fddf5b7ae44ef4fa91d2246a146d28304d7ac" +checksum = "98d4bb4cbce0f519100ba0d6aa5541aa69aa00b0f61bdb862c81124f9cf38cea" dependencies = [ "erg_common", "erg_compiler", @@ -166,9 +166,9 @@ dependencies = [ [[package]] name = "erg_common" -version = "0.6.49" +version = "0.6.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6b12918165bc24a49d35897b6b4535360a0dec5b0641d9c1849fe0345875cc1" +checksum = "15fcd8b1d8d47238d1488f7a05a8131b77b89adb54c867327b83db272a919344" dependencies = [ "backtrace-on-stack-overflow", "erg_proc_macros", @@ -179,9 +179,9 @@ dependencies = [ [[package]] name = "erg_compiler" -version = "0.6.49" +version = "0.6.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212f2c20609d69579e8f0d860fcad6a6c0aa6434b84a92f24336da339725df52" +checksum = "4a5b708d63c430435aac418e0822ef435b6b26c5338e5795130cce308f319505" dependencies = [ "erg_common", "erg_parser", @@ -189,9 +189,9 @@ dependencies = [ [[package]] name = "erg_parser" -version = "0.6.49" +version = "0.6.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ae50e2439a5922d664a9a43cd116b4c30eac39b25724ef5af25c719a30b8234" +checksum = "6b79c7b5789c93deeeb21cbe9d4e0c62db60712a187a1c260210079d3750b4be" dependencies = [ "erg_common", "erg_proc_macros", @@ -200,9 +200,9 @@ dependencies = [ [[package]] name = "erg_proc_macros" -version = "0.6.49" +version = "0.6.50" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e2799a04d59d8dfd8f4d8a0a495354203d4efb5f48fe2f8effc521e80026714" +checksum = "99659bb992c4e9da4af751d63fa126034025ffe79e07bfdbf013d9e165e768fc" dependencies = [ "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index ec1ce72..d2a9d99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,9 +24,9 @@ edition = "2021" repository = "https://github.com/mtshiba/pylyzer" [workspace.dependencies] -erg_common = { version = "0.6.49", features = ["py_compat", "els"] } -erg_compiler = { version = "0.6.49", features = ["py_compat", "els"] } -els = { version = "0.1.61", features = ["py_compat"] } +erg_common = { version = "0.6.50", features = ["py_compat", "els"] } +erg_compiler = { version = "0.6.50", features = ["py_compat", "els"] } +els = { version = "0.1.50", features = ["py_compat"] } # rustpython-parser = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] } # rustpython-ast = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] } rustpython-parser = { git = "https://github.com/RustPython/Parser", version = "0.4.0", features = ["all-nodes-with-ranges", "location"] } diff --git a/README.md b/README.md index 3d6c748..debf377 100644 --- a/README.md +++ b/README.md @@ -123,6 +123,7 @@ pylyzer converts Python ASTs to Erg ASTs and passes them to Erg's type checker. * [x] function/method * [x] class * [ ] `async/await` + * [ ] user-defined abstract class * [x] type inference * [x] variable * [x] operator @@ -165,10 +166,13 @@ pylyzer converts Python ASTs to Erg ASTs and passes them to Erg's type checker. * [x] type narrowing * [ ] others * [ ] `collections.abc` + * [x] `Collection` + * [x] `Container` + * [x] `Generator` * [x] `Iterable` * [x] `Iterator` - * [x] `Mapping` - * [x] `Sequence` + * [x] `Mapping`, `MutableMapping` + * [x] `Sequence`, `MutableSequence` * [ ] others * [x] type assertion (`typing.cast`) * [x] type narrowing (`is`, `isinstance`) diff --git a/crates/py2erg/convert.rs b/crates/py2erg/convert.rs index 0a877ab..0d2c16c 100644 --- a/crates/py2erg/convert.rs +++ b/crates/py2erg/convert.rs @@ -17,9 +17,10 @@ use erg_compiler::erg_parser::ast::{ KeyValue, KwArg, Lambda, LambdaSignature, List, ListComprehension, Literal, Methods, Module, NonDefaultParamSignature, NormalDict, NormalList, NormalRecord, NormalSet, NormalTuple, ParamPattern, ParamTySpec, Params, PosArg, PreDeclTypeSpec, ReDef, Record, RecordAttrs, Set, - SetComprehension, Signature, SubrSignature, SubrTypeSpec, Tuple, TupleTypeSpec, TypeAscription, - TypeBoundSpec, TypeBoundSpecs, TypeSpec, TypeSpecWithOp, UnaryOp, VarName, VarPattern, - VarRecordAttr, VarRecordAttrs, VarRecordPattern, VarSignature, VisModifierSpec, + SetComprehension, Signature, SubrSignature, SubrTypeSpec, Tuple, TupleTypeSpec, TypeAppArgs, + TypeAppArgsKind, TypeAscription, TypeBoundSpec, TypeBoundSpecs, TypeSpec, TypeSpecWithOp, + UnaryOp, VarName, VarPattern, VarRecordAttr, VarRecordAttrs, VarRecordPattern, VarSignature, + VisModifierSpec, }; use erg_compiler::erg_parser::desugar::Desugarer; use erg_compiler::erg_parser::token::{Token, TokenKind, AS, COLON, DOT, EQUAL}; @@ -991,43 +992,54 @@ impl ASTConverter { Lambda::new(sig, op, Block::new(body), DefId(0)) } - fn convert_ident_type_spec(&mut self, name: String, loc: PyLocation) -> TypeSpec { + fn convert_ident_type_spec(&mut self, name: String, range: PySourceRange) -> TypeSpec { + let loc = pyloc_to_ergloc(range); match &name[..] { // Iterable[T] => Iterable(T), Iterable => Iterable(Obj) global_unary_collections!() => TypeSpec::poly( - ConstExpr::Accessor(ConstAccessor::Local(Identifier::private("global".into()))) - .attr(Identifier::private(name.into())), + ConstExpr::Accessor(ConstAccessor::Local(Identifier::private_with_loc( + "global".into(), + loc, + ))) + .attr(Identifier::private_with_loc(name.into(), loc)), ConstArgs::single(ConstExpr::Accessor(ConstAccessor::Local( - Identifier::private("Obj".into()), + Identifier::private_with_loc("Obj".into(), loc), ))), ), // MutableSequence[T] => Sequence!(T), MutableSequence => Sequence!(Obj) global_mutable_unary_collections!() => TypeSpec::poly( - ConstExpr::Accessor(ConstAccessor::Local(Identifier::private("global".into()))) - .attr(Identifier::private( - format!("{}!", name.trim_start_matches("Mutable")).into(), - )), + ConstExpr::Accessor(ConstAccessor::Local(Identifier::private_with_loc( + "global".into(), + loc, + ))) + .attr(Identifier::private_with_loc( + format!("{}!", name.trim_start_matches("Mutable")).into(), + loc, + )), ConstArgs::single(ConstExpr::Accessor(ConstAccessor::Local( - Identifier::private("Obj".into()), + Identifier::private_with_loc("Obj".into(), loc), ))), ), // Mapping => Mapping(Obj, Obj) global_binary_collections!() => TypeSpec::poly( - ConstExpr::Accessor(ConstAccessor::Local(Identifier::private("global".into()))) - .attr(Identifier::private(name.into())), + ConstExpr::Accessor(ConstAccessor::Local(Identifier::private_with_loc( + "global".into(), + loc, + ))) + .attr(Identifier::private_with_loc(name.into(), loc)), ConstArgs::pos_only( vec![ ConstPosArg::new(ConstExpr::Accessor(ConstAccessor::Local( - Identifier::private("Obj".into()), + Identifier::private_with_loc("Obj".into(), loc), ))), ConstPosArg::new(ConstExpr::Accessor(ConstAccessor::Local( - Identifier::private("Obj".into()), + Identifier::private_with_loc("Obj".into(), loc), ))), ], None, ), ), - _ => TypeSpec::mono(self.convert_ident(name, loc)), + _ => TypeSpec::mono(self.convert_ident(name, range.start)), } } @@ -1427,13 +1439,13 @@ impl ASTConverter { .unwrap() .appeared_type_names .insert(name.id.to_string()); - self.convert_ident_type_spec(name.id.to_string(), name.location()) + self.convert_ident_type_spec(name.id.to_string(), name.range) } py_ast::Expr::Constant(cons) => { if cons.value.is_none() { - self.convert_ident_type_spec("NoneType".into(), cons.location()) + self.convert_ident_type_spec("NoneType".into(), cons.range) } else if let Some(name) = cons.value.as_str() { - self.convert_ident_type_spec(name.into(), cons.location()) + self.convert_ident_type_spec(name.into(), cons.range) } else { let err = CompileError::syntax_error( self.cfg.input.clone(), @@ -1458,8 +1470,7 @@ impl ASTConverter { global_unary_collections!() | global_mutable_unary_collections!() | global_binary_collections!() => { - return self - .convert_ident_type_spec(attr.attr.to_string(), attr.range.start) + return self.convert_ident_type_spec(attr.attr.to_string(), attr.range) } "Any" => return TypeSpec::PreDeclTy(PreDeclTypeSpec::Mono(t)), _ => {} @@ -2218,9 +2229,19 @@ impl ASTConverter { &mut self, ident: Identifier, body: Vec, - inherit: bool, + base: Option, ) -> (Option, Vec) { - let class = TypeSpec::mono(ident.clone()); + let inherit = base.is_some(); + let class = if let Some(base) = base { + let base_spec = self.convert_type_spec(base.clone()); + let expr = self.convert_expr(base); + let loc = expr.loc(); + let base = TypeSpecWithOp::new(COLON, base_spec, expr); + let args = TypeAppArgs::new(loc, TypeAppArgsKind::SubtypeOf(Box::new(base)), loc); + TypeSpec::type_app(TypeSpec::mono(ident.clone()), args) + } else { + TypeSpec::mono(ident.clone()) + }; let class_as_expr = Expr::Accessor(Accessor::Ident(ident)); let (base_type, attrs) = self.extract_method(body, inherit); self.block_id_counter += 1; @@ -2398,12 +2419,13 @@ impl ASTConverter { .into_iter() .map(|deco| self.convert_expr(deco)) .collect::>(); + let inherit = class_def.bases.first().cloned(); + let is_inherit = inherit.is_some(); let mut bases = class_def .bases .into_iter() .map(|base| self.convert_expr(base)) .collect::>(); - let inherit = !bases.is_empty(); self.register_name_info(&name, NameKind::Class); let class_name_loc = PyLocation { row: loc.row, @@ -2413,7 +2435,7 @@ impl ASTConverter { let sig = Signature::Var(VarSignature::new(VarPattern::Ident(ident.clone()), None)); self.grow(ident.inspect().to_string(), BlockKind::Class); let (base_type, methods) = self.extract_method_list(ident, class_def.body, inherit); - let classdef = if inherit { + let classdef = if is_inherit { // TODO: multiple inheritance let pos_args = vec![PosArg::new(bases.remove(0))]; let mut args = Args::pos_only(pos_args, None); diff --git a/tests/abc.py b/tests/abc.py new file mode 100644 index 0000000..b224a97 --- /dev/null +++ b/tests/abc.py @@ -0,0 +1,19 @@ +from collections.abc import Sequence + +class Vec(Sequence): + x: list[int] + + def __init__(self): + self.x = [] + + def __getitem__(self, i: int) -> int: + return self.x[i] + + def __iter__(self): + return iter(self.x) + + def __len__(self) -> int: + return len(self.x) + + def __contains__(self, i: int) -> bool: + return i in self.x diff --git a/tests/test.rs b/tests/test.rs index 8cd7130..019a412 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -59,6 +59,11 @@ pub fn expect(file_path: &'static str, warns: usize, errors: usize) -> Result<() exec_new_thread(move || _expect(file_path, warns, errors), file_path) } +#[test] +fn exec_abc() -> Result<(), String> { + expect("tests/abc.py", 0, 0) +} + #[test] fn exec_test() -> Result<(), String> { expect("tests/test.py", 0, 11)