feat: ABC implementation check

This commit is contained in:
Shunsuke Shibayama 2024-12-27 15:46:59 +09:00
parent 233b7ee382
commit 2a98535d4c
6 changed files with 91 additions and 41 deletions

20
Cargo.lock generated
View file

@ -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",

View file

@ -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"] }

View file

@ -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`)

View file

@ -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<py_ast::Stmt>,
inherit: bool,
base: Option<py_ast::Expr>,
) -> (Option<Expr>, Vec<Methods>) {
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::<Vec<_>>();
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::<Vec<_>>();
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);

19
tests/abc.py Normal file
View file

@ -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

View file

@ -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)