diff --git a/Cargo.lock b/Cargo.lock index b323c48..eed114e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,9 +139,9 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "els" -version = "0.1.54-nightly.3" +version = "0.1.54-nightly.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe2679dec933507f4d9abfa4e6468d3b312790e933b3269ce2a2a2bc910bd3d" +checksum = "bdc6282121d9e2871553e0731cfb88119cbfe76ce8aab08ac2be01f5644e3bee" dependencies = [ "erg_common", "erg_compiler", @@ -153,9 +153,9 @@ dependencies = [ [[package]] name = "erg_common" -version = "0.6.42-nightly.3" +version = "0.6.42-nightly.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c5582717e4cd56c2015263641441c7f3c19ae030bd804565f0b93ad0a8c536e" +checksum = "c0dfc622cc65f230a05a284a21f62c8f4a3c964c51c97881cc4e01202ef2a3c0" dependencies = [ "backtrace-on-stack-overflow", "erg_proc_macros", @@ -165,9 +165,9 @@ dependencies = [ [[package]] name = "erg_compiler" -version = "0.6.42-nightly.3" +version = "0.6.42-nightly.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "299406202ab6dfe28be95c3d6ceae19d9821624ef666a978b42112f658b528c4" +checksum = "42247c4ab1eb33ed3e2e9e74f4773565eba4491f76bfbda4b965015938704ca1" dependencies = [ "erg_common", "erg_parser", @@ -175,9 +175,9 @@ dependencies = [ [[package]] name = "erg_parser" -version = "0.6.42-nightly.3" +version = "0.6.42-nightly.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc13d9b7c3342e1a4ccd4159e39c99769a30733ac6cf44c22593d0aecb169af9" +checksum = "6c921c178517c2071e45418e8c5b35e0c3a19021e4459283fd8f6997b89ba216" dependencies = [ "erg_common", "erg_proc_macros", @@ -186,9 +186,9 @@ dependencies = [ [[package]] name = "erg_proc_macros" -version = "0.6.42-nightly.3" +version = "0.6.42-nightly.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8506a5228f462df55923b4a6ca8617ce90a84b52a4b603cedf3dda6beb3243f" +checksum = "c1381ca7a7a0781834cb1d617cd8361cca23f880cb9c515d249905d585832734" dependencies = [ "quote", "syn 1.0.109", diff --git a/Cargo.toml b/Cargo.toml index 085dcb4..e0781a3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,9 @@ edition = "2021" repository = "https://github.com/mtshiba/pylyzer" [workspace.dependencies] -erg_common = { version = "0.6.42-nightly.3", features = ["py_compat", "els"] } -erg_compiler = { version = "0.6.42-nightly.3", features = ["py_compat", "els"] } -els = { version = "0.1.54-nightly.3", features = ["py_compat"] } +erg_common = { version = "0.6.42-nightly.5", features = ["py_compat", "els"] } +erg_compiler = { version = "0.6.42-nightly.5", features = ["py_compat", "els"] } +els = { version = "0.1.54-nightly.5", 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 2ce5734..5f13ab8 100644 --- a/crates/py2erg/convert.rs +++ b/crates/py2erg/convert.rs @@ -221,6 +221,7 @@ pub enum ShadowingMode { #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct TypeVarInfo { name: String, + constraints: Vec, bound: Option, } @@ -235,8 +236,12 @@ impl fmt::Display for TypeVarInfo { } impl TypeVarInfo { - pub const fn new(name: String, bound: Option) -> Self { - Self { name, bound } + pub const fn new(name: String, constraints: Vec, bound: Option) -> Self { + Self { + name, + constraints, + bound, + } } } @@ -1662,6 +1667,7 @@ impl ASTConverter { fn get_type_bounds(&mut self, type_params: Vec) -> TypeBoundSpecs { let mut bounds = TypeBoundSpecs::empty(); + let mut errs = vec![]; if type_params.is_empty() { for ty in self.cur_appeared_type_names() { let name = VarName::from_str(ty.clone().into()); @@ -1672,6 +1678,42 @@ impl ASTConverter { .unwrap_or(TypeSpec::Infer(name.token().clone())); let spec = TypeSpecWithOp::new(op, t_spec, bound.clone()); TypeBoundSpec::non_default(name, spec) + } else if !tv_info.constraints.is_empty() { + if tv_info.constraints.len() == 1 { + let err = CompileError::syntax_error( + self.cfg.input.clone(), + line!() as usize, + pyloc_to_ergloc(type_params[0].range()), + self.cur_namespace(), + "TypeVar must have at least two constrained types".into(), + None, + ); + errs.push(err); + } + let op = Token::dummy(TokenKind::Colon, ":"); + let mut elems = vec![]; + for constraint in tv_info.constraints.iter() { + if let Ok(expr) = Parser::validate_const_expr(constraint.clone()) { + elems.push(ConstPosArg::new(expr)); + } + } + let t_spec = TypeSpec::Enum(ConstArgs::pos_only(elems, None)); + let elems = Args::pos_only( + tv_info + .constraints + .iter() + .cloned() + .map(PosArg::new) + .collect(), + None, + ); + let expr = Expr::Set(Set::Normal(NormalSet::new( + Token::DUMMY, + Token::DUMMY, + elems, + ))); + let spec = TypeSpecWithOp::new(op, t_spec, expr); + TypeBoundSpec::non_default(name, spec) } else { TypeBoundSpec::Omitted(name) }; @@ -1696,6 +1738,7 @@ impl ASTConverter { }; bounds.push(spec); } + self.errs.extend(errs); bounds } @@ -1893,8 +1936,14 @@ impl ASTConverter { } else { name.id.to_string() }; - let bound = call.args.nth_or_key(1, "bound").cloned(); - let info = TypeVarInfo::new(arg, bound); + let mut constraints = vec![]; + let mut nth = 1; + while let Some(constr) = call.args.get_nth(nth) { + constraints.push(constr.clone()); + nth += 1; + } + let bound = call.args.get_with_key("bound").cloned(); + let info = TypeVarInfo::new(arg, constraints, bound); self.define_type_var(name.id.to_string(), info); } } diff --git a/tests/class.py b/tests/class.py index ee31761..f425fa9 100644 --- a/tests/class.py +++ b/tests/class.py @@ -71,3 +71,10 @@ class F: _ = F(1) _ = F(1, 2) _ = F(1, z=1, y=2) + +class G(DoesNotExist): # ERR + def foo(self): + return 1 + +g = G() +assert g.foo() == 1 diff --git a/tests/test.rs b/tests/test.rs index 0c4a843..072639c 100644 --- a/tests/test.rs +++ b/tests/test.rs @@ -84,7 +84,7 @@ fn exec_func() -> Result<(), String> { #[test] fn exec_class() -> Result<(), String> { - expect("tests/class.py", 0, 5) + expect("tests/class.py", 0, 6) } #[test] @@ -139,7 +139,7 @@ fn exec_shadowing() -> Result<(), String> { #[test] fn exec_typevar() -> Result<(), String> { - expect("tests/typevar.py", 0, 2) + expect("tests/typevar.py", 0, 3) } #[test] diff --git a/tests/typevar.py b/tests/typevar.py index b00f7eb..ecb4d84 100644 --- a/tests/typevar.py +++ b/tests/typevar.py @@ -1,17 +1,24 @@ from typing import TypeVar T = TypeVar("T") -U = TypeVar("U", int) +U = TypeVar("U", bound=int) +IS = TypeVar("IS", int, str) def id(x: T) -> T: return x def id_int(x: U) -> U: return x +def id_int_or_str(x: IS) -> IS: + return x + _ = id(1) + 1 # OK _ = id("a") + "b" # OK _ = id_int(1) # OK _ = id_int("a") # ERR +_ = id_int_or_str(1) # OK +_ = id_int_or_str("a") # OK +_ = id_int_or_str(None) # ERR def id2[T](x: T) -> T: return x