Skip to content

Commit

Permalink
fix: Python 3.9 bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
mtshiba committed Sep 12, 2023
1 parent f157673 commit 75c1ac7
Show file tree
Hide file tree
Showing 8 changed files with 104 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
38 changes: 30 additions & 8 deletions crates/erg_compiler/codegen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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(),
Expand All @@ -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(),
Expand All @@ -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;
}

Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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
_ => {
Expand Down Expand Up @@ -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")),
)],
);
}
Expand Down
2 changes: 1 addition & 1 deletion crates/erg_compiler/lib/std/_erg_array.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
16 changes: 8 additions & 8 deletions crates/erg_compiler/lib/std/_erg_contains_operator.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -10,30 +10,30 @@ 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
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, 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:
Expand All @@ -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:
Expand Down
6 changes: 6 additions & 0 deletions crates/erg_compiler/lib/std/_erg_int.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
38 changes: 25 additions & 13 deletions crates/erg_compiler/lib/std/_erg_type.py
Original file line number Diff line number Diff line change
@@ -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
3 changes: 2 additions & 1 deletion crates/erg_compiler/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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;
Expand Down
38 changes: 31 additions & 7 deletions tests/embed.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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(())
}

Expand Down

0 comments on commit 75c1ac7

Please sign in to comment.