feat: support `@property`

This commit is contained in:
Shunsuke Shibayama 2025-01-01 20:56:40 +09:00
parent accd453a12
commit dedfa8443b
6 changed files with 92 additions and 15 deletions

20
Cargo.lock generated
View File

@ -150,9 +150,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "els"
version = "0.1.63-nightly.0"
version = "0.1.63-nightly.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "99d2ae54e13d256ec9a09554249ccc498c31022836c76bc5dc1b11b072f0a753"
checksum = "499bf0ea920c33a3b8f905450435135a8fbefc6c06c3b53f2b5c0e66d25367b5"
dependencies = [
"erg_common",
"erg_compiler",
@ -166,9 +166,9 @@ dependencies = [
[[package]]
name = "erg_common"
version = "0.6.51-nightly.0"
version = "0.6.51-nightly.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abc654d256215d0d1b2ccb5d1e80c9188148dd59714d0a642f5738bf0271197a"
checksum = "1d112c6737def3a5ee868510f4ce9ef9be64b2a0bdf8899b24576a1e8521d401"
dependencies = [
"backtrace-on-stack-overflow",
"erg_proc_macros",
@ -179,9 +179,9 @@ dependencies = [
[[package]]
name = "erg_compiler"
version = "0.6.51-nightly.0"
version = "0.6.51-nightly.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "223b817462901cfef987f38c21a18f9637f7f796dbe58a3b51a1e09489fd25be"
checksum = "32d241b882842c86c6c3e2aa54c4c8121fdbedd8a15a7b563cf5f7c1c5c0c0bb"
dependencies = [
"erg_common",
"erg_parser",
@ -189,9 +189,9 @@ dependencies = [
[[package]]
name = "erg_parser"
version = "0.6.51-nightly.0"
version = "0.6.51-nightly.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13d9eaf0b3076b05cb7290be98de5c1cbd73ab7a72b090f67d5911e03d062501"
checksum = "91efa61b1495ee680394b6f9d98d84535dc8445329f0310734a3e40671a0061a"
dependencies = [
"erg_common",
"erg_proc_macros",
@ -200,9 +200,9 @@ dependencies = [
[[package]]
name = "erg_proc_macros"
version = "0.6.51-nightly.0"
version = "0.6.51-nightly.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3d2e883cddea276b76add108da8d16276d9d6962a0079561515e701121a3e1a"
checksum = "bd78027648c82db68efed1a1789bd84ce09f07ea7630085c9015c4ed6b1a192d"
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.51-nightly.0", features = ["py_compat", "els"] }
erg_compiler = { version = "0.6.51-nightly.0", features = ["py_compat", "els"] }
els = { version = "0.1.63-nightly.0", features = ["py_compat"] }
erg_common = { version = "0.6.51-nightly.1", features = ["py_compat", "els"] }
erg_compiler = { version = "0.6.51-nightly.1", features = ["py_compat", "els"] }
els = { version = "0.1.63-nightly.1", 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

@ -2261,10 +2261,12 @@ impl ASTConverter {
&mut self,
body: Vec<py_ast::Stmt>,
inherit: bool,
class_name: Expr,
) -> (Option<Expr>, ClassAttrs) {
let mut base_type = None;
let mut attrs = vec![];
let mut init_is_defined = false;
let mut call_params_len = None;
for stmt in body {
match self.convert_statement(stmt, true) {
Expr::Def(mut def) => {
@ -2274,12 +2276,55 @@ impl ASTConverter {
.insert(Decorator(Expr::static_local("Override")));
}
}
if def
if def.sig.decorators().is_some_and(|decos| {
decos.iter().any(|deco| {
deco.expr()
.get_name()
.is_some_and(|name| name == "property")
})
}) {
// class Foo:
// @property
// def foo(self): ...
// ↓
// class Foo:
// def foo_(self): ...
// foo = Foo(*[]).foo_()
let mut args = Args::empty();
if call_params_len.as_ref().is_some_and(|&len| len >= 1) {
args.set_var_args(PosArg::new(Expr::List(List::Normal(
NormalList::new(
Token::dummy(TokenKind::LSqBr, "["),
Token::dummy(TokenKind::RSqBr, "]"),
Args::empty(),
),
))));
}
let instance = class_name.clone().call(args);
let name = def.sig.ident().unwrap().clone();
def.sig
.ident_mut()
.unwrap()
.name
.rename(format!("{} ", name.inspect()).into());
let escaped = def.sig.ident().unwrap().clone();
let call = Expr::Call(instance).method_call_expr(escaped, Args::empty());
let t_spec = def.sig.t_spec_op_mut().cloned();
let sig =
Signature::Var(VarSignature::new(VarPattern::Ident(name), t_spec));
let var_def =
Def::new(sig, DefBody::new(EQUAL, Block::new(vec![call]), DefId(0)));
attrs.push(ClassAttr::Def(def));
attrs.push(ClassAttr::Def(var_def));
} else if def
.sig
.ident()
.is_some_and(|id| &id.inspect()[..] == "__init__")
{
if let Some(call_def) = self.extract_init(&mut base_type, def) {
if let Some(params) = call_def.sig.params() {
call_params_len = Some(params.len());
}
attrs.insert(0, ClassAttr::Def(call_def));
init_is_defined = true;
}
@ -2355,7 +2400,7 @@ impl ASTConverter {
TypeSpec::mono(ident.clone())
};
let class_as_expr = Expr::Accessor(Accessor::Ident(ident));
let (base_type, attrs) = self.extract_method(body, inherit);
let (base_type, attrs) = self.extract_method(body, inherit, class_as_expr.clone());
self.block_id_counter += 1;
let methods = Methods::new(
DefId(self.block_id_counter),

11
tests/err/property.py Normal file
View File

@ -0,0 +1,11 @@
class Foo:
x: int
def __init__(self, x):
self.x = x
@property
def foo(self):
return self.x
f = Foo(1)
print(f.foo + "a") # ERR

11
tests/property.py Normal file
View File

@ -0,0 +1,11 @@
class Foo:
x: int
def __init__(self, x):
self.x = x
@property
def foo(self):
return self.x
f = Foo(1)
assert f.foo + 1 == 2

View File

@ -128,6 +128,16 @@ fn exec_projection() -> Result<(), String> {
expect("tests/projection.py", 0, 5)
}
#[test]
fn exec_property() -> Result<(), String> {
expect("tests/property.py", 0, 0)
}
#[test]
fn exec_property_err() -> Result<(), String> {
expect("tests/err/property.py", 0, 1)
}
#[test]
fn exec_pyi() -> Result<(), String> {
expect("tests/pyi.py", 0, 5)