Skip to content

Commit f157673

Browse files
committed
fix: dynamic container type check
1 parent bfa429a commit f157673

File tree

8 files changed

+94
-10
lines changed

8 files changed

+94
-10
lines changed

crates/erg_compiler/codegen.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,7 @@ pub struct PyCodeGenerator {
184184
control_loaded: bool,
185185
convertors_loaded: bool,
186186
operators_loaded: bool,
187+
union_loaded: bool,
187188
abc_loaded: bool,
188189
unit_size: usize,
189190
units: PyCodeGenStack,
@@ -204,6 +205,7 @@ impl PyCodeGenerator {
204205
control_loaded: false,
205206
convertors_loaded: false,
206207
operators_loaded: false,
208+
union_loaded: false,
207209
abc_loaded: false,
208210
unit_size: 0,
209211
units: PyCodeGenStack::empty(),
@@ -224,6 +226,7 @@ impl PyCodeGenerator {
224226
control_loaded: false,
225227
convertors_loaded: false,
226228
operators_loaded: false,
229+
union_loaded: false,
227230
abc_loaded: false,
228231
unit_size: 0,
229232
units: PyCodeGenStack::empty(),
@@ -244,6 +247,7 @@ impl PyCodeGenerator {
244247
self.control_loaded = false;
245248
self.convertors_loaded = false;
246249
self.operators_loaded = false;
250+
self.union_loaded = false;
247251
self.abc_loaded = false;
248252
}
249253

@@ -1566,6 +1570,16 @@ impl PyCodeGenerator {
15661570
self.emit_push_null();
15671571
self.emit_load_name_instr(Identifier::public("OpenRange"));
15681572
}
1573+
// From 3.10, `or` can be used for types.
1574+
// But Erg supports Python 3.7~, so we should use `typing.Union`.
1575+
TokenKind::OrOp if bin.lhs.ref_t().is_type() => {
1576+
self.load_union();
1577+
// self.emit_push_null();
1578+
self.emit_load_name_instr(Identifier::private("#Union"));
1579+
let args = Args::pos_only(vec![PosArg::new(*bin.lhs), PosArg::new(*bin.rhs)], None);
1580+
self.emit_index_args(args);
1581+
return;
1582+
}
15691583
TokenKind::ContainsOp => {
15701584
// if no-std, always `x contains y == True`
15711585
if self.cfg.no_std {
@@ -3517,6 +3531,16 @@ impl PyCodeGenerator {
35173531
);
35183532
}
35193533

3534+
fn load_union(&mut self) {
3535+
self.emit_global_import_items(
3536+
Identifier::public("typing"),
3537+
vec![(
3538+
Identifier::public("Union"),
3539+
Some(Identifier::private("#Union")),
3540+
)],
3541+
);
3542+
}
3543+
35203544
fn load_module_type(&mut self) {
35213545
self.emit_global_import_items(
35223546
Identifier::public("types"),

crates/erg_compiler/lib/std/_erg_array.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def __getitem__(self, index_or_slice):
5353

5454
def type_check(self, t: type) -> bool:
5555
if isinstance(t, list):
56-
if len(t) != len(self):
56+
if len(t) < len(self):
5757
return False
5858
for (inner_t, elem) in zip(t, self):
5959
if not contains_operator(inner_t, elem):

crates/erg_compiler/lib/std/_erg_contains_operator.py

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from _erg_result import is_ok
22
from _erg_range import Range
3+
from _erg_type import is_type, isinstance
34

45
from collections import namedtuple
56

@@ -8,7 +9,7 @@ def contains_operator(y, elem) -> bool:
89
if hasattr(elem, "type_check"):
910
return elem.type_check(y)
1011
# 1 in Int
11-
elif type(y) == type:
12+
elif is_type(y):
1213
if isinstance(elem, y):
1314
return True
1415
elif hasattr(y, "try_new") and is_ok(y.try_new(elem)):
@@ -17,26 +18,33 @@ def contains_operator(y, elem) -> bool:
1718
return False
1819
# [1] in [Int]
1920
elif isinstance(y, list) and isinstance(elem, list) and (
20-
type(y[0]) == type or isinstance(y[0], Range)
21+
len(y) == 0 or is_type(y[0]) or isinstance(y[0], Range)
2122
):
22-
# FIXME:
23-
type_check = contains_operator(y[0], elem[0])
24-
len_check = len(elem) == len(y)
23+
type_check = all(map(lambda x: contains_operator(x[0], x[1]), zip(y, elem)))
24+
len_check = len(elem) <= len(y)
2525
return type_check and len_check
2626
# (1, 2) in (Int, Int)
2727
elif isinstance(y, tuple) and isinstance(elem, tuple) and (
28-
type(y[0]) == type or isinstance(y[0], Range)
28+
len(y) == 0 or is_type(y[0]) or isinstance(y[0], Range)
2929
):
3030
if not hasattr(elem, "__iter__"):
3131
return False
3232
type_check = all(map(lambda x: contains_operator(x[0], x[1]), zip(y, elem)))
33-
len_check = len(elem) == len(y)
33+
len_check = len(elem) <= len(y)
3434
return type_check and len_check
3535
# {1: 2} in {Int: Int}
36-
elif isinstance(y, dict) and isinstance(elem, dict) and isinstance(next(iter(y.keys())), type):
36+
elif isinstance(y, dict) and isinstance(elem, dict) and (
37+
len(y) == 0 or is_type(next(iter(y.keys())))
38+
):
39+
if len(y) == 1:
40+
key = next(iter(y.keys()))
41+
key_check = all([contains_operator(key, el) for el in elem.keys()])
42+
value = next(iter(y.values()))
43+
value_check = all([contains_operator(value, el) for el in elem.values()])
44+
return key_check and value_check
3745
# TODO:
3846
type_check = True # contains_operator(next(iter(y.keys())), x[next(iter(x.keys()))])
39-
len_check = len(elem) >= len(y)
47+
len_check = True # It can be True even if either elem or y has the larger number of elems
4048
return type_check and len_check
4149
elif isinstance(elem, list):
4250
from _erg_array import Array
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import _GenericAlias, Union
2+
try:
3+
from types import UnionType
4+
except ImportError:
5+
class UnionType:
6+
__args__: list # list[type]
7+
def __init__(self, *args):
8+
self.__args__ = args
9+
10+
def is_type(x) -> bool:
11+
return isinstance(x, type) or \
12+
isinstance(x, _GenericAlias) or \
13+
isinstance(x, UnionType)
14+
15+
instanceof = isinstance
16+
# The behavior of `builtins.isinstance` depends on the Python version.
17+
def isinstance(obj, classinfo) -> bool:
18+
if instanceof(classinfo, _GenericAlias) and classinfo.__origin__ == Union:
19+
return any(instanceof(obj, t) for t in classinfo.__args__)
20+
else:
21+
return instanceof(obj, classinfo)

crates/erg_compiler/transpile.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,7 @@ impl PyScriptGenerator {
371371
.replace("from _erg_result import is_ok", "")
372372
.replace("from _erg_control import then__", "")
373373
.replace("from _erg_contains_operator import contains_operator", "")
374+
.replace("from _erg_type import is_type, isinstance", "")
374375
}
375376

376377
fn load_namedtuple_if_not(&mut self) {

tests/should_ok/assert_cast.er

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ assert j["a"] in Array(Int)
1212
assert j["a"] notin Array(Str)
1313
_: Array(Int) = j["a"]
1414

15+
dic = {"a": "b", "c": "d"}
16+
assert dic in {Str: {"b", "d"}}
17+
assert dic in {Str: Str}
18+
1519
.f dic: {Str: Str or Array(Str)} =
1620
assert dic["key"] in Str # Required to pass the check on the next line
1721
assert dic["key"] in {"a", "b", "c"}

tests/should_ok/dyn_type_check.er

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
assert 1 in (Int or Str)
2+
assert 1.2 notin (Int or Str)
3+
4+
dic = {:}
5+
assert dic in {:}
6+
assert dic in {Str: Int}
7+
assert dic in {Str: Str}
8+
dic2 = {"a": 1}
9+
assert dic2 in {Str or Int: Int}
10+
assert dic2 in {Str: Int or Str}
11+
assert dic2 notin {Int: Int}
12+
13+
tup = ()
14+
assert tup in ()
15+
assert tup in (Int, Int)
16+
assert tup in (Int, Str)
17+
18+
arr = []
19+
assert arr in []
20+
assert arr in [Int]
21+
assert arr in [Str]

tests/test.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,11 @@ fn exec_dict_test() -> Result<(), ()> {
100100
expect_success("tests/should_ok/dict.er", 0)
101101
}
102102

103+
#[test]
104+
fn exec_empty_check() -> Result<(), ()> {
105+
expect_success("tests/should_ok/dyn_type_check.er", 0)
106+
}
107+
103108
#[test]
104109
fn exec_external() -> Result<(), ()> {
105110
let py_command = opt_which_python().unwrap();

0 commit comments

Comments
 (0)