Skip to content

Commit

Permalink
fix: dynamic container type check
Browse files Browse the repository at this point in the history
  • Loading branch information
mtshiba committed Sep 12, 2023
1 parent bfa429a commit 97762e1
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 10 deletions.
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 @@ -53,7 +53,7 @@ def __getitem__(self, index_or_slice):

def type_check(self, t: type) -> bool:
if isinstance(t, list):
if len(t) != len(self):
if len(t) < len(self):
return False
for (inner_t, elem) in zip(t, self):
if not contains_operator(inner_t, elem):
Expand Down
26 changes: 17 additions & 9 deletions crates/erg_compiler/lib/std/_erg_contains_operator.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from _erg_result import is_ok
from _erg_range import Range
from _erg_type import is_type

from collections import namedtuple

Expand All @@ -8,7 +9,7 @@ def contains_operator(y, elem) -> bool:
if hasattr(elem, "type_check"):
return elem.type_check(y)
# 1 in Int
elif type(y) == type:
elif is_type(y):
if isinstance(elem, y):
return True
elif hasattr(y, "try_new") and is_ok(y.try_new(elem)):
Expand All @@ -17,26 +18,33 @@ def contains_operator(y, elem) -> bool:
return False
# [1] in [Int]
elif isinstance(y, list) and isinstance(elem, list) and (
type(y[0]) == type or isinstance(y[0], Range)
len(y) == 0 or is_type(y[0]) or isinstance(y[0], Range)
):
# FIXME:
type_check = contains_operator(y[0], elem[0])
len_check = len(elem) == len(y)
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 (
type(y[0]) == type or isinstance(y[0], Range)
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)
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 isinstance(next(iter(y.keys())), type):
elif isinstance(y, dict) and isinstance(elem, dict) and (
len(y) == 0 or is_type(next(iter(y.keys())))
):
if len(y) == 1:
key = next(iter(y.keys()))
key_check = all([contains_operator(key, el) for el in elem.keys()])
value = next(iter(y.values()))
value_check = all([contains_operator(value, el) for el in elem.values()])
return key_check and value_check
# TODO:
type_check = True # contains_operator(next(iter(y.keys())), x[next(iter(x.keys()))])
len_check = len(elem) >= len(y)
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):
from _erg_array import Array
Expand Down
5 changes: 5 additions & 0 deletions crates/erg_compiler/lib/std/_erg_type.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from types import UnionType

def is_type(x) -> bool:
return isinstance(x, type) or \
isinstance(x, UnionType)
1 change: 1 addition & 0 deletions crates/erg_compiler/transpile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +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", "")
}

fn load_namedtuple_if_not(&mut self) {
Expand Down
4 changes: 4 additions & 0 deletions tests/should_ok/assert_cast.er
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ assert j["a"] in Array(Int)
assert j["a"] notin Array(Str)
_: Array(Int) = j["a"]

dic = {"a": "b", "c": "d"}
assert dic in {Str: {"b", "d"}}
assert dic in {Str: Str}

.f dic: {Str: Str or Array(Str)} =
assert dic["key"] in Str # Required to pass the check on the next line
assert dic["key"] in {"a", "b", "c"}
21 changes: 21 additions & 0 deletions tests/should_ok/dyn_type_check.er
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
assert 1 in (Int or Str)
assert 1.2 notin (Int or Str)

dic = {:}
assert dic in {:}
assert dic in {Str: Int}
assert dic in {Str: Str}
dic2 = {"a": 1}
assert dic2 in {Str or Int: Int}
assert dic2 in {Str: Int or Str}
assert dic2 notin {Int: Int}

tup = ()
assert tup in ()
assert tup in (Int, Int)
assert tup in (Int, Str)

arr = []
assert arr in []
assert arr in [Int]
assert arr in [Str]
5 changes: 5 additions & 0 deletions tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ fn exec_dict_test() -> Result<(), ()> {
expect_success("tests/should_ok/dict.er", 0)
}

#[test]
fn exec_empty_check() -> Result<(), ()> {
expect_success("tests/should_ok/dyn_type_check.er", 0)
}

#[test]
fn exec_external() -> Result<(), ()> {
let py_command = opt_which_python().unwrap();
Expand Down

0 comments on commit 97762e1

Please sign in to comment.