fix: dynamic container type check

This commit is contained in:
Shunsuke Shibayama 2023-09-12 22:19:02 +09:00
parent bfa429a347
commit f157673c59
8 changed files with 94 additions and 10 deletions

View file

@ -184,6 +184,7 @@ pub struct PyCodeGenerator {
control_loaded: bool,
convertors_loaded: bool,
operators_loaded: bool,
union_loaded: bool,
abc_loaded: bool,
unit_size: usize,
units: PyCodeGenStack,
@ -204,6 +205,7 @@ impl PyCodeGenerator {
control_loaded: false,
convertors_loaded: false,
operators_loaded: false,
union_loaded: false,
abc_loaded: false,
unit_size: 0,
units: PyCodeGenStack::empty(),
@ -224,6 +226,7 @@ impl PyCodeGenerator {
control_loaded: false,
convertors_loaded: false,
operators_loaded: false,
union_loaded: false,
abc_loaded: false,
unit_size: 0,
units: PyCodeGenStack::empty(),
@ -244,6 +247,7 @@ impl PyCodeGenerator {
self.control_loaded = false;
self.convertors_loaded = false;
self.operators_loaded = false;
self.union_loaded = false;
self.abc_loaded = false;
}
@ -1566,6 +1570,16 @@ impl PyCodeGenerator {
self.emit_push_null();
self.emit_load_name_instr(Identifier::public("OpenRange"));
}
// From 3.10, `or` can be used for types.
// 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);
return;
}
TokenKind::ContainsOp => {
// if no-std, always `x contains y == True`
if self.cfg.no_std {
@ -3517,6 +3531,16 @@ impl PyCodeGenerator {
);
}
fn load_union(&mut self) {
self.emit_global_import_items(
Identifier::public("typing"),
vec![(
Identifier::public("Union"),
Some(Identifier::private("#Union")),
)],
);
}
fn load_module_type(&mut self) {
self.emit_global_import_items(
Identifier::public("types"),

View file

@ -53,7 +53,7 @@ class Array(list):
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):

View file

@ -1,5 +1,6 @@
from _erg_result import is_ok
from _erg_range import Range
from _erg_type import is_type, isinstance
from collections import namedtuple
@ -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)):
@ -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

View file

@ -0,0 +1,21 @@
from typing import _GenericAlias, Union
try:
from types import UnionType
except ImportError:
class UnionType:
__args__: list # list[type]
def __init__(self, *args):
self.__args__ = args
def is_type(x) -> bool:
return isinstance(x, type) or \
isinstance(x, _GenericAlias) or \
isinstance(x, 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__)
else:
return instanceof(obj, classinfo)

View file

@ -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, isinstance", "")
}
fn load_namedtuple_if_not(&mut self) {