feat: attrs can be registered in methods other than `__init__`

This commit is contained in:
Shunsuke Shibayama 2025-02-22 10:52:14 +09:00
parent 5fb0c71a63
commit 7248c00448
5 changed files with 191 additions and 94 deletions

56
Cargo.lock generated
View File

@ -31,9 +31,9 @@ dependencies = [
[[package]] [[package]]
name = "anyhow" name = "anyhow"
version = "1.0.95" version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" checksum = "6b964d184e89d9b6b67dd2715bc8e74cf3107fb2b529990c90cf517326150bf4"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
@ -93,9 +93,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.14" version = "1.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3d1b2e905a3a7b00a6141adb0e4c0bb941d11caf55349d863942a1cc44e3c9" checksum = "c736e259eea577f443d5c86c304f9f4ae0295c43f3ba05c21f1d66b5f06001af"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -152,9 +152,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]] [[package]]
name = "els" name = "els"
version = "0.1.65-nightly.1" version = "0.1.65-nightly.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93c98062a1591263c7bdb7444ffae5eb7151fb3fc8f09eddeba13163e86706cf" checksum = "1dd708f21ce87a184ff5c9010afe61169b021a986fa1f0480ce93f7069308b34"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_compiler", "erg_compiler",
@ -168,9 +168,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_common" name = "erg_common"
version = "0.6.53-nightly.1" version = "0.6.53-nightly.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6d0fdaad7b1913c304d070a5e4737e7495e2eadca1a67fddca77649995c4d52" checksum = "fc22c2d3966dfd49dc4e3d142a4c066acfbc2debbf807bd80239640263f82906"
dependencies = [ dependencies = [
"backtrace-on-stack-overflow", "backtrace-on-stack-overflow",
"erg_proc_macros", "erg_proc_macros",
@ -181,9 +181,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_compiler" name = "erg_compiler"
version = "0.6.53-nightly.1" version = "0.6.53-nightly.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26ebe561fa98ff75a6a99167e86826f01baca96e0a696f3fcb4af86101f9c309" checksum = "33a71fd95255b255147476dba4b043babe82350d1b32c3bd2b716b5c801ac455"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_parser", "erg_parser",
@ -191,9 +191,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_parser" name = "erg_parser"
version = "0.6.53-nightly.1" version = "0.6.53-nightly.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c06268f345b640bcb6b3f0bf2390b9da072b05b72c697f51502e47e11db667" checksum = "283375be7368b88ab745eeb2530c4a276325d36d41f664935c6521cd2afe4043"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_proc_macros", "erg_proc_macros",
@ -202,9 +202,9 @@ dependencies = [
[[package]] [[package]]
name = "erg_proc_macros" name = "erg_proc_macros"
version = "0.6.53-nightly.1" version = "0.6.53-nightly.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b6f03ae04101a0079ac52948edd33a08b3349d78a5cf632bddc22f020bd52e1" checksum = "222a96d889f1f49149a1d20e634096a82fc575dfe49661d8e713ebcfb152ef71"
dependencies = [ dependencies = [
"quote", "quote",
"syn 1.0.109", "syn 1.0.109",
@ -468,9 +468,9 @@ dependencies = [
[[package]] [[package]]
name = "log" name = "log"
version = "0.4.25" version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]] [[package]]
name = "lsp-types" name = "lsp-types"
@ -560,9 +560,9 @@ dependencies = [
[[package]] [[package]]
name = "miniz_oxide" name = "miniz_oxide"
version = "0.8.4" version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b" checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [ dependencies = [
"adler2", "adler2",
] ]
@ -798,9 +798,9 @@ dependencies = [
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.5.8" version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" checksum = "82b568323e98e49e2a0899dcee453dd679fae22d69adf9b11dd508d1549b7e2f"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
] ]
@ -890,18 +890,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.217" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]] [[package]]
name = "serde_derive" name = "serde_derive"
version = "1.0.217" version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -910,9 +910,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.138" version = "1.0.139"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949" checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -1077,9 +1077,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.16" version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe"
[[package]] [[package]]
name = "unicode-width" name = "unicode-width"

View File

@ -24,9 +24,9 @@ edition = "2021"
repository = "https://github.com/mtshiba/pylyzer" repository = "https://github.com/mtshiba/pylyzer"
[workspace.dependencies] [workspace.dependencies]
erg_common = { version = "0.6.53-nightly.1", features = ["py_compat", "els"] } erg_common = { version = "0.6.53-nightly.2", features = ["py_compat", "els"] }
erg_compiler = { version = "0.6.53-nightly.1", features = ["py_compat", "els"] } erg_compiler = { version = "0.6.53-nightly.2", features = ["py_compat", "els"] }
els = { version = "0.1.65-nightly.1", features = ["py_compat"] } els = { version = "0.1.65-nightly.2", features = ["py_compat"] }
# rustpython-parser = { version = "0.3.0", features = ["all-nodes-with-ranges", "location"] } # 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-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"] } rustpython-parser = { git = "https://github.com/RustPython/Parser", version = "0.4.0", features = ["all-nodes-with-ranges", "location"] }

View File

@ -6,7 +6,7 @@ use erg_common::dict::Dict as HashMap;
use erg_common::error::Location as ErgLocation; use erg_common::error::Location as ErgLocation;
use erg_common::fresh::FRESH_GEN; use erg_common::fresh::FRESH_GEN;
use erg_common::set::Set as HashSet; use erg_common::set::Set as HashSet;
use erg_common::traits::{Locational, Stream}; use erg_common::traits::{Locational, Stream, Traversable};
use erg_common::{fmt_vec, log, set}; use erg_common::{fmt_vec, log, set};
use erg_compiler::artifact::IncompleteArtifact; use erg_compiler::artifact::IncompleteArtifact;
use erg_compiler::erg_parser::ast::{ use erg_compiler::erg_parser::ast::{
@ -2165,32 +2165,59 @@ impl ASTConverter {
Block::new(new_block) Block::new(new_block)
} }
fn check_init_sig(&mut self, sig: &Signature) -> Option<()> { #[allow(clippy::result_large_err)]
fn check_init_sig(&self, sig: &Signature) -> Result<(), (bool, CompileError)> {
match sig { match sig {
Signature::Subr(subr) => { Signature::Subr(subr) => {
if let Some(first) = subr.params.non_defaults.first() { if let Some(first) = subr.params.non_defaults.first() {
if first.inspect().map(|s| &s[..]) == Some("self") { if first.inspect().map(|s| &s[..]) == Some("self") {
return Some(()); return Ok(());
} }
} }
self.errs.push(self_not_found_error( Err((
self.cfg.input.clone(), true,
subr.loc(), self_not_found_error(self.cfg.input.clone(), subr.loc(), self.cur_namespace()),
self.cur_namespace(), ))
));
Some(())
}
Signature::Var(var) => {
self.errs.push(init_var_error(
self.cfg.input.clone(),
var.loc(),
self.cur_namespace(),
));
None
} }
Signature::Var(var) => Err((
false,
init_var_error(self.cfg.input.clone(), var.loc(), self.cur_namespace()),
)),
} }
} }
// ```python
// def __init__(self, x: Int, y: Int, z):
// self.x = x
// self.y = y
// if True:
// self.set_z(z)
//
// def set_z(self, z):
// self.z = z
// ```
// ↓
// methods: {"set_z"}
#[allow(clippy::only_used_in_recursion)]
fn collect_called_methods(&self, expr: &Expr, methods: &mut HashSet<String>) {
expr.traverse(&mut |ex| {
if let Expr::Call(call) = ex {
match call.obj.as_ref() {
Expr::Accessor(Accessor::Ident(ident)) if ident.inspect() == "self" => {
if let Some(method_ident) = &call.attr_name {
methods.insert(method_ident.inspect().to_string());
} else {
self.collect_called_methods(&call.obj, methods);
}
}
_ => self.collect_called_methods(ex, methods),
}
} else {
self.collect_called_methods(ex, methods);
}
});
}
// def __init__(self, x: Int, y: Int, z): // def __init__(self, x: Int, y: Int, z):
// self.x = x // self.x = x
// self.y = y // self.y = y
@ -2198,8 +2225,21 @@ impl ASTConverter {
// ↓ // ↓
// requirement : {x: Int, y: Int, z: Any} // requirement : {x: Int, y: Int, z: Any}
// returns : .__call__(x: Int, y: Int, z: Obj): Self = .unreachable() // returns : .__call__(x: Int, y: Int, z: Obj): Self = .unreachable()
fn extract_init(&mut self, base_type: &mut Option<Expr>, init_def: Def) -> Option<Def> { fn extract_init(
self.check_init_sig(&init_def.sig)?; &mut self,
base_type: &mut Option<Expr>,
init_def: Def,
attrs: &[ClassAttr],
pre_check: bool,
) -> Option<Def> {
if let Err((continuable, err)) = self.check_init_sig(&init_def.sig) {
if pre_check {
self.errs.push(err);
}
if !continuable {
return None;
}
}
let l_brace = Token::new( let l_brace = Token::new(
TokenKind::LBrace, TokenKind::LBrace,
"{", "{",
@ -2212,50 +2252,21 @@ impl ASTConverter {
init_def.ln_end().unwrap_or(0), init_def.ln_end().unwrap_or(0),
init_def.col_end().unwrap_or(0), init_def.col_end().unwrap_or(0),
); );
let expr = Expr::Def(init_def.clone());
let Signature::Subr(sig) = init_def.sig else { let Signature::Subr(sig) = init_def.sig else {
unreachable!() unreachable!()
}; };
let mut fields = vec![]; let mut fields = vec![];
for chunk in init_def.body.block { self.extract_instance_attrs(&sig, &init_def.body.block, &mut fields);
#[allow(clippy::single_match)] let mut method_names = HashSet::new();
match chunk { self.collect_called_methods(&expr, &mut method_names);
Expr::ReDef(redef) => { for class_attr in attrs {
let Accessor::Attr(attr) = redef.attr else { if let ClassAttr::Def(def) = class_attr {
continue; if let Signature::Subr(sig) = &def.sig {
}; if method_names.contains(&sig.ident.inspect()[..]) {
// if `self.foo == ...` self.extract_instance_attrs(sig, &def.body.block, &mut fields);
if attr.obj.get_name().map(|s| &s[..]) == Some("self") {
// get attribute types
let typ = if let Some(t_spec_op) = sig
.params
.non_defaults
.iter()
.find(|&param| param.inspect() == Some(attr.ident.inspect()))
.and_then(|param| param.t_spec.as_ref())
.or_else(|| {
sig.params
.defaults
.iter()
.find(|&param| param.inspect() == Some(attr.ident.inspect()))
.and_then(|param| param.sig.t_spec.as_ref())
}) {
*t_spec_op.t_spec_as_expr.clone()
} else if let Some(typ) = redef.t_spec.map(|t_spec| t_spec.t_spec_as_expr) {
*typ
} else {
Expr::from(Accessor::Ident(Identifier::private_with_line(
"Any".into(),
attr.obj.ln_begin().unwrap_or(0),
)))
};
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);
fields.push(field_type_def);
} }
} }
_ => {}
} }
} }
if let Some(Expr::Record(Record::Normal(rec))) = base_type.as_mut() { if let Some(Expr::Record(Record::Normal(rec))) = base_type.as_mut() {
@ -2312,6 +2323,55 @@ impl ASTConverter {
Some(def) Some(def)
} }
fn extract_instance_attrs(&self, sig: &SubrSignature, block: &Block, fields: &mut Vec<Def>) {
for chunk in block.iter() {
if let Expr::ReDef(redef) = chunk {
let Accessor::Attr(attr) = &redef.attr else {
continue;
};
// if `self.foo == ...`
if attr.obj.get_name().map(|s| &s[..]) == Some("self")
&& fields
.iter()
.all(|field| field.sig.ident().unwrap() != &attr.ident)
{
// get attribute types
let typ = if let Some(t_spec_op) = sig
.params
.non_defaults
.iter()
.find(|&param| param.inspect() == Some(attr.ident.inspect()))
.and_then(|param| param.t_spec.as_ref())
.or_else(|| {
sig.params
.defaults
.iter()
.find(|&param| param.inspect() == Some(attr.ident.inspect()))
.and_then(|param| param.sig.t_spec.as_ref())
}) {
*t_spec_op.t_spec_as_expr.clone()
} else if let Some(typ) =
redef.t_spec.clone().map(|t_spec| t_spec.t_spec_as_expr)
{
*typ
} else {
Expr::from(Accessor::Ident(Identifier::private_with_line(
"Any".into(),
attr.obj.ln_begin().unwrap_or(0),
)))
};
let sig = Signature::Var(VarSignature::new(
VarPattern::Ident(attr.ident.clone()),
None,
));
let body = DefBody::new(EQUAL, Block::new(vec![typ]), DefId(0));
let field_type_def = Def::new(sig, body);
fields.push(field_type_def);
}
}
}
}
fn gen_default_init(&self, line: usize) -> Def { fn gen_default_init(&self, line: usize) -> Def {
let call_ident = Identifier::new( let call_ident = Identifier::new(
VisModifierSpec::Public(ErgLocation::Unknown), VisModifierSpec::Public(ErgLocation::Unknown),
@ -2346,7 +2406,6 @@ impl ASTConverter {
) -> (Option<Expr>, ClassAttrs) { ) -> (Option<Expr>, ClassAttrs) {
let mut base_type = None; let mut base_type = None;
let mut attrs = vec![]; let mut attrs = vec![];
let mut init_is_defined = false;
let mut call_params_len = None; let mut call_params_len = None;
for stmt in body { for stmt in body {
match self.convert_statement(stmt, true) { match self.convert_statement(stmt, true) {
@ -2402,12 +2461,15 @@ impl ASTConverter {
.ident() .ident()
.is_some_and(|id| &id.inspect()[..] == "__init__") .is_some_and(|id| &id.inspect()[..] == "__init__")
{ {
if let Some(call_def) = self.extract_init(&mut base_type, def) { // We will generate `__init__` and determine the shape of the class at the end
// Here we just extract the signature
if let Some(call_def) =
self.extract_init(&mut base_type, def.clone(), &attrs, true)
{
if let Some(params) = call_def.sig.params() { if let Some(params) = call_def.sig.params() {
call_params_len = Some(params.len()); call_params_len = Some(params.len());
} }
attrs.insert(0, ClassAttr::Def(call_def)); attrs.push(ClassAttr::Def(def));
init_is_defined = true;
} }
} else { } else {
attrs.push(ClassAttr::Def(def)); attrs.push(ClassAttr::Def(def));
@ -2457,7 +2519,25 @@ impl ASTConverter {
_other => {} // TODO: _other => {} // TODO:
} }
} }
if !init_is_defined && !inherit { let mut init = None;
for (i, attr) in attrs.iter().enumerate() {
if let ClassAttr::Def(def) = attr {
if def
.sig
.ident()
.is_some_and(|id| &id.inspect()[..] == "__init__")
{
if let Some(def) = self.extract_init(&mut base_type, def.clone(), &attrs, false)
{
init = Some((i, def));
}
}
}
}
if let Some((i, def)) = init {
attrs.remove(i);
attrs.insert(0, ClassAttr::Def(def));
} else if !inherit {
attrs.insert(0, ClassAttr::Def(self.gen_default_init(0))); attrs.insert(0, ClassAttr::Def(self.gen_default_init(0)));
} }
(base_type, ClassAttrs::new(attrs)) (base_type, ClassAttrs::new(attrs))

View File

@ -120,3 +120,20 @@ class Cs:
self.cs.append(c) self.cs.append(c)
self.cs2.append(c) self.cs2.append(c)
self.cs_list.append([c]) self.cs_list.append([c])
class I:
def __init__(self):
self.ix: int = 1
if True:
self.init_y()
def init_y(self):
self.iy: int = 2
def foo(self):
self.iz: int = 1 # ERR
i = I()
_ = i.ix
_ = i.iy # OK
_ = i.iz # ERR

View File

@ -100,7 +100,7 @@ fn exec_func() -> Result<(), String> {
#[test] #[test]
fn exec_class() -> Result<(), String> { fn exec_class() -> Result<(), String> {
expect("tests/class.py", 0, 6) expect("tests/class.py", 0, 8)
} }
#[test] #[test]