From 5fb0c71a63ffe3222bd157e8f764994187a24cc7 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Fri, 21 Feb 2025 21:22:13 +0900 Subject: [PATCH] fix: `List` type bug --- Cargo.lock | 20 ++++++++--------- Cargo.toml | 6 ++--- crates/py2erg/convert.rs | 48 +++++++++++++++++++++++++++++++++++----- tests/class.py | 17 +++++++++++++- tests/err/class.py | 14 ++++++++++++ tests/test.rs | 2 +- 6 files changed, 87 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5f8f596..5a21fbe 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -152,9 +152,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "els" -version = "0.1.65-nightly.0" +version = "0.1.65-nightly.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e47271869da4eb87b3fe853c99c1c36faf5a2aa42e59df37d4d03683dfa47d" +checksum = "93c98062a1591263c7bdb7444ffae5eb7151fb3fc8f09eddeba13163e86706cf" dependencies = [ "erg_common", "erg_compiler", @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "erg_common" -version = "0.6.53-nightly.0" +version = "0.6.53-nightly.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80f2bf84c599bbfa0186d90cf0e6894cc3d0c05b33b33bebddf4bae9082a386f" +checksum = "a6d0fdaad7b1913c304d070a5e4737e7495e2eadca1a67fddca77649995c4d52" dependencies = [ "backtrace-on-stack-overflow", "erg_proc_macros", @@ -181,9 +181,9 @@ dependencies = [ [[package]] name = "erg_compiler" -version = "0.6.53-nightly.0" +version = "0.6.53-nightly.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59e0f2f1c2e7369bdd9f80dc4c3d3452eedb7f8e7911eae60ab6784449592234" +checksum = "26ebe561fa98ff75a6a99167e86826f01baca96e0a696f3fcb4af86101f9c309" dependencies = [ "erg_common", "erg_parser", @@ -191,9 +191,9 @@ dependencies = [ [[package]] name = "erg_parser" -version = "0.6.53-nightly.0" +version = "0.6.53-nightly.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28892f334a901d746c9aeb4e70c20661387b1edcdf126c9f3e3c34d6405428ab" +checksum = "d4c06268f345b640bcb6b3f0bf2390b9da072b05b72c697f51502e47e11db667" dependencies = [ "erg_common", "erg_proc_macros", @@ -202,9 +202,9 @@ dependencies = [ [[package]] name = "erg_proc_macros" -version = "0.6.53-nightly.0" +version = "0.6.53-nightly.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c22150bc5756a07cf110e7b66fcd3f7f6fdea8a3b9146b1a2a227e3ede6eb0c" +checksum = "2b6f03ae04101a0079ac52948edd33a08b3349d78a5cf632bddc22f020bd52e1" dependencies = [ "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index 4a8284e..6b48f61 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.53-nightly.0", features = ["py_compat", "els"] } -erg_compiler = { version = "0.6.53-nightly.0", features = ["py_compat", "els"] } -els = { version = "0.1.65-nightly.0", features = ["py_compat"] } +erg_common = { version = "0.6.53-nightly.1", features = ["py_compat", "els"] } +erg_compiler = { version = "0.6.53-nightly.1", features = ["py_compat", "els"] } +els = { version = "0.1.65-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"] } diff --git a/crates/py2erg/convert.rs b/crates/py2erg/convert.rs index ff85f14..4923ba3 100644 --- a/crates/py2erg/convert.rs +++ b/crates/py2erg/convert.rs @@ -144,12 +144,26 @@ fn escape_name(name: String) -> String { "tuple" => "GenericTuple".into(), "type" => "Type".into(), "ModuleType" => "GeneticModule".into(), - "MutableSequence" => "Sequence!".into(), - "MutableMapping" => "Mapping!".into(), _ => name, } } +fn is_global_poly_type(name: &str) -> bool { + matches!( + name, + "list" + | "dict" + | "set" + | "tuple" + | "List" + | "Dict" + | "Tuple" + | global_unary_collections!() + | global_mutable_unary_collections!() + | global_binary_collections!() + ) +} + fn quoted_symbol(sym: &str, lineno: u32, col_begin: u32) -> Token { let col_end = col_begin + sym.chars().count() as u32; Token { @@ -241,6 +255,7 @@ impl DefinedPlace { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct NameInfo { + kind: NameKind, rename: Option, defined_in: DefinedPlace, defined_block_id: usize, @@ -250,12 +265,14 @@ pub struct NameInfo { impl NameInfo { pub fn new( + kind: NameKind, rename: Option, defined_in: DefinedPlace, defined_block_id: usize, defined_times: usize, ) -> Self { Self { + kind, rename, defined_in, defined_block_id, @@ -636,6 +653,15 @@ impl ASTConverter { None } + fn is_type_name(&self, name: &str) -> bool { + is_global_poly_type(name) + || self + .contexts + .iter() + .rev() + .any(|ctx| ctx.names.get(name).is_some_and(|ni| ni.kind.is_class())) + } + fn define_name(&mut self, name: String, info: NameInfo) { self.contexts.last_mut().unwrap().names.insert(name, info); } @@ -732,7 +758,7 @@ impl ASTConverter { None }; let defined_in = DefinedPlace::Known(self.cur_namespace()); - let info = NameInfo::new(rename, defined_in, self.cur_block_id(), 1); + let info = NameInfo::new(kind, rename, defined_in, self.cur_block_id(), 1); self.define_name(String::from(name), info); RenameKind::Let } @@ -773,7 +799,7 @@ impl ASTConverter { } else { DefinedPlace::Unknown }; - let mut info = NameInfo::new(None, defined_in, cur_block_id, 0); + let mut info = NameInfo::new(NameKind::Variable, None, defined_in, cur_block_id, 0); info.add_referrer(cur_namespace); self.declare_name(name.clone(), info); name @@ -1527,7 +1553,6 @@ impl ASTConverter { } fn convert_type_spec(&mut self, expr: py_ast::Expr) -> TypeSpec { - #[allow(clippy::collapsible_match)] match expr { py_ast::Expr::Name(name) => { self.contexts @@ -2000,6 +2025,19 @@ impl ASTConverter { } py_ast::Expr::Subscript(subs) => { let obj = self.convert_expr(*subs.value); + // List[T] => List!(T) + if obj.get_name().is_some_and(|n| self.is_type_name(n)) { + let obj = if obj.get_name().is_some_and(|n| n == "List") { + let global = self.convert_ident("global".to_string(), subs.range.start); + Expr::from(global).attr_expr(Identifier::private("List!".into())) + } else if obj.get_name().is_some_and(|n| n == "Set") { + let global = self.convert_ident("global".to_string(), subs.range.start); + Expr::from(global).attr_expr(Identifier::private("Set!".into())) + } else { + obj + }; + return obj.call1(self.convert_expr(*subs.slice)); + } let method = obj.attr_expr( self.convert_ident("__getitem__".to_string(), subs.slice.location()), ); diff --git a/tests/class.py b/tests/class.py index 452359b..86b867a 100644 --- a/tests/class.py +++ b/tests/class.py @@ -1,4 +1,4 @@ -from typing import Self +from typing import Self, List class Empty: pass emp = Empty() @@ -105,3 +105,18 @@ class Implicit: def set_foo(self): self.foo = True + +class Cs: + cs: list[C] + cs2: List[C] + cs_list: list[list[C]] + + def __init__(self, cs: list[C]): + self.cs = cs + self.cs2 = cs + self.cs_list = [] + + def add(self, c: C): + self.cs.append(c) + self.cs2.append(c) + self.cs_list.append([c]) diff --git a/tests/err/class.py b/tests/err/class.py index ee6dfd7..4257d12 100644 --- a/tests/err/class.py +++ b/tests/err/class.py @@ -2,3 +2,17 @@ class Foo: def invalid_append(self): paths: list[str] = [] paths.append(self) # ERR + +class Bar: + foos: list[Foo] + + def __init__(self, foos: list[Foo]) -> None: + self.foos = foos + + def add_foo(self, foo: Foo): + self.foos.append(foo) + + def invalid_add_foo(self): + self.foos.append(1) # ERR + +_ = Bar([Bar([])]) # ERR diff --git a/tests/test.rs b/tests/test.rs index f7ad9b6..b609be3 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -105,7 +105,7 @@ fn exec_class() -> Result<(), String> { #[test] fn exec_class_err() -> Result<(), String> { - expect("tests/err/class.py", 0, 1) + expect("tests/err/class.py", 0, 3) } #[test]