diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 24ca9fdf6..9d5f1a6b6 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -35,7 +35,7 @@ jobs: fail-fast: false matrix: os: [windows-latest, ubuntu-latest, macos-latest] - python-version: ['3.10', '3.11.3'] + python-version: ['3.9', '3.10', '3.11.3'] runs-on: ${{ matrix.os }} env: RUST_BACKTRACE: full diff --git a/crates/erg_compiler/codegen.rs b/crates/erg_compiler/codegen.rs index 24384eef1..85f6ea7a8 100644 --- a/crates/erg_compiler/codegen.rs +++ b/crates/erg_compiler/codegen.rs @@ -185,6 +185,7 @@ pub struct PyCodeGenerator { convertors_loaded: bool, operators_loaded: bool, union_loaded: bool, + fake_generic_loaded: bool, abc_loaded: bool, unit_size: usize, units: PyCodeGenStack, @@ -206,6 +207,7 @@ impl PyCodeGenerator { convertors_loaded: false, operators_loaded: false, union_loaded: false, + fake_generic_loaded: false, abc_loaded: false, unit_size: 0, units: PyCodeGenStack::empty(), @@ -227,6 +229,7 @@ impl PyCodeGenerator { convertors_loaded: false, operators_loaded: false, union_loaded: false, + fake_generic_loaded: false, abc_loaded: false, unit_size: 0, units: PyCodeGenStack::empty(), @@ -248,6 +251,7 @@ impl PyCodeGenerator { self.convertors_loaded = false; self.operators_loaded = false; self.union_loaded = false; + self.fake_generic_loaded = false; self.abc_loaded = false; } @@ -1574,10 +1578,10 @@ impl PyCodeGenerator { // But Erg supports Python 3.7~, so we should use `typing.Union`. TokenKind::OrOp if bin.lhs.ref_t().is_type() => { self.load_union(); - // self.emit_push_null(); - self.emit_load_name_instr(Identifier::private("#Union")); let args = Args::pos_only(vec![PosArg::new(*bin.lhs), PosArg::new(*bin.rhs)], None); - self.emit_index_args(args); + self.emit_push_null(); + self.emit_load_name_instr(Identifier::private("#UnionType")); + self.emit_args_311(args, Name, false); return; } TokenKind::ContainsOp => { @@ -2453,8 +2457,16 @@ impl PyCodeGenerator { _ => todo!("not supported Python version"), }, other if local.ref_t().is_poly_type_meta() && other != "classof" => { - self.emit_load_name_instr(local); - self.emit_index_args(args); + if self.py_version.minor <= Some(9) { + self.load_fake_generic(); + self.emit_load_name_instr(Identifier::private("#FakeGenericAlias")); + let mut args = args; + args.insert_pos(0, PosArg::new(Expr::Accessor(Accessor::Ident(local)))); + self.emit_args_311(args, Name, false); + } else { + self.emit_load_name_instr(local); + self.emit_index_args(args); + } } // "pyimport" | "py" are here _ => { @@ -3533,10 +3545,20 @@ impl PyCodeGenerator { fn load_union(&mut self) { self.emit_global_import_items( - Identifier::public("typing"), + Identifier::public("_erg_type"), + vec![( + Identifier::public("UnionType"), + Some(Identifier::private("#UnionType")), + )], + ); + } + + fn load_fake_generic(&mut self) { + self.emit_global_import_items( + Identifier::public("_erg_type"), vec![( - Identifier::public("Union"), - Some(Identifier::private("#Union")), + Identifier::public("FakeGenericAlias"), + Some(Identifier::private("#FakeGenericAlias")), )], ); } diff --git a/crates/erg_compiler/lib/std/_erg_array.py b/crates/erg_compiler/lib/std/_erg_array.py index 0284b8e1d..42c6d0c49 100644 --- a/crates/erg_compiler/lib/std/_erg_array.py +++ b/crates/erg_compiler/lib/std/_erg_array.py @@ -7,7 +7,7 @@ class Array(list): def try_new(arr): # -> Result[Array] if isinstance(arr, list): - return Array(a) + return Array(arr) else: return Error("not a list") diff --git a/crates/erg_compiler/lib/std/_erg_contains_operator.py b/crates/erg_compiler/lib/std/_erg_contains_operator.py index b247d7be7..a10009ee6 100644 --- a/crates/erg_compiler/lib/std/_erg_contains_operator.py +++ b/crates/erg_compiler/lib/std/_erg_contains_operator.py @@ -1,6 +1,6 @@ from _erg_result import is_ok from _erg_range import Range -from _erg_type import is_type, isinstance +from _erg_type import is_type, _isinstance from collections import namedtuple @@ -10,22 +10,22 @@ def contains_operator(y, elem) -> bool: return elem.type_check(y) # 1 in Int elif is_type(y): - if isinstance(elem, y): + if _isinstance(elem, y): return True elif hasattr(y, "try_new") and is_ok(y.try_new(elem)): return True # TODO: trait check return False # [1] in [Int] - elif isinstance(y, list) and isinstance(elem, list) and ( - len(y) == 0 or is_type(y[0]) or isinstance(y[0], Range) + elif _isinstance(y, list) and _isinstance(elem, list) and ( + len(y) == 0 or is_type(y[0]) or _isinstance(y[0], Range) ): type_check = all(map(lambda x: contains_operator(x[0], x[1]), zip(y, elem))) len_check = len(elem) <= len(y) return type_check and len_check # (1, 2) in (Int, Int) - elif isinstance(y, tuple) and isinstance(elem, tuple) and ( - len(y) == 0 or is_type(y[0]) or isinstance(y[0], Range) + elif _isinstance(y, tuple) and _isinstance(elem, tuple) and ( + len(y) == 0 or is_type(y[0]) or _isinstance(y[0], Range) ): if not hasattr(elem, "__iter__"): return False @@ -33,7 +33,7 @@ def contains_operator(y, elem) -> bool: len_check = len(elem) <= len(y) return type_check and len_check # {1: 2} in {Int: Int} - elif isinstance(y, dict) and isinstance(elem, dict) and ( + elif _isinstance(y, dict) and _isinstance(elem, dict) and ( len(y) == 0 or is_type(next(iter(y.keys()))) ): if len(y) == 1: @@ -46,7 +46,7 @@ def contains_operator(y, elem) -> bool: type_check = True # contains_operator(next(iter(y.keys())), x[next(iter(x.keys()))]) len_check = True # It can be True even if either elem or y has the larger number of elems return type_check and len_check - elif isinstance(elem, list): + elif _isinstance(elem, list): from _erg_array import Array return contains_operator(y, Array(elem)) else: diff --git a/crates/erg_compiler/lib/std/_erg_int.py b/crates/erg_compiler/lib/std/_erg_int.py index fc0feb195..a3784cb0c 100644 --- a/crates/erg_compiler/lib/std/_erg_int.py +++ b/crates/erg_compiler/lib/std/_erg_int.py @@ -9,6 +9,12 @@ def try_new(i): # -> Result[Nat] else: return Error("not an integer") + def bit_count(self): + if hasattr(int, "bit_count"): + return int.bit_count(self) + else: + return bin(self).count("1") + def succ(self): return Int(self + 1) diff --git a/crates/erg_compiler/lib/std/_erg_type.py b/crates/erg_compiler/lib/std/_erg_type.py index 26af8622c..c38092da6 100644 --- a/crates/erg_compiler/lib/std/_erg_type.py +++ b/crates/erg_compiler/lib/std/_erg_type.py @@ -1,21 +1,33 @@ -from typing import _GenericAlias, Union -try: - from types import UnionType -except ImportError: - class UnionType: +from typing import Union + +class UnionType: + __origin__ = Union __args__: list # list[type] def __init__(self, *args): self.__args__ = args +class FakeGenericAlias: + __origin__: type + __args__: list # list[type] + def __init__(self, origin, *args): + self.__origin__ = origin + self.__args__ = args +try: + from types import GenericAlias +except ImportError: + GenericAlias = FakeGenericAlias + def is_type(x) -> bool: - return isinstance(x, type) or \ - isinstance(x, _GenericAlias) or \ - isinstance(x, UnionType) + return isinstance(x, (type, GenericAlias, UnionType)) -instanceof = isinstance # The behavior of `builtins.isinstance` depends on the Python version. -def isinstance(obj, classinfo) -> bool: - if instanceof(classinfo, _GenericAlias) and classinfo.__origin__ == Union: - return any(instanceof(obj, t) for t in classinfo.__args__) +def _isinstance(obj, classinfo) -> bool: + if isinstance(classinfo, (GenericAlias, UnionType)): + if classinfo.__origin__ == Union: + return any(isinstance(obj, t) for t in classinfo.__args__) + else: + return isinstance(obj, classinfo.__origin__) + elif is_type(classinfo): + return isinstance(obj, classinfo) else: - return instanceof(obj, classinfo) + return False diff --git a/crates/erg_compiler/transpile.rs b/crates/erg_compiler/transpile.rs index 714f77b86..06ad08c55 100644 --- a/crates/erg_compiler/transpile.rs +++ b/crates/erg_compiler/transpile.rs @@ -371,7 +371,7 @@ impl PyScriptGenerator { .replace("from _erg_result import is_ok", "") .replace("from _erg_control import then__", "") .replace("from _erg_contains_operator import contains_operator", "") - .replace("from _erg_type import is_type, isinstance", "") + .replace("from _erg_type import is_type, _isinstance", "") } fn load_namedtuple_if_not(&mut self) { @@ -397,6 +397,7 @@ impl PyScriptGenerator { if !self.contains_op_loaded { self.prelude += &Self::replace_import(include_str!("lib/std/_erg_result.py")); self.prelude += &Self::replace_import(include_str!("lib/std/_erg_range.py")); + self.prelude += &Self::replace_import(include_str!("lib/std/_erg_type.py")); self.prelude += &Self::replace_import(include_str!("lib/std/_erg_contains_operator.py")); self.contains_op_loaded = true; diff --git a/tests/embed.rs b/tests/embed.rs index 4dbc58a14..14c952f93 100644 --- a/tests/embed.rs +++ b/tests/embed.rs @@ -1,7 +1,8 @@ use erg::DummyVM; use erg_common::config::ErgConfig; use erg_common::error::MultiErrorDisplay; -use erg_common::python_util::exec_py_code_with_output; +use erg_common::fn_name; +use erg_common::python_util::{env_python_version, exec_py_code_with_output}; use erg_compiler::artifact::Buildable; use erg_compiler::module::SharedCompilerResource; use erg_compiler::HIRBuilder; @@ -42,11 +43,7 @@ fn test_transpiler_embedding2() -> Result<(), ()> { let res = trans .transpile( " -print!(0, end:=\"\") -i = match [1, 2]: - [2, 1] -> 0 - [1, 2] -> 1 -print!(i, end:=\"\") +print!(1, end:=\"\") j = if False: do: 1 do: 2 @@ -66,7 +63,34 @@ while! do! c < 7, do!: })?; let res = exec_py_code_with_output(res.object.code(), &[]).map_err(|_| ())?; assert!(res.status.success()); - assert_eq!(res.stdout, b"0123456"); + assert_eq!(res.stdout, b"123456"); + Ok(()) +} + +#[test] +fn test_transpiler_embedding3() -> Result<(), ()> { + if env_python_version().minor < Some(10) { + println!("skipped: {}", fn_name!()); + return Ok(()); + } + let mut trans = Transpiler::default(); + let res = trans + .transpile( + " +i = match [1, 2]: + [2, 1] -> 0 + [1, 2] -> 1 +print!(i, end:=\"\") +" + .into(), + "exec", + ) + .map_err(|es| { + es.errors.write_all_stderr(); + })?; + let res = exec_py_code_with_output(res.object.code(), &[]).map_err(|_| ())?; + assert!(res.status.success()); + assert_eq!(res.stdout, b"1"); Ok(()) }