fix: dynamic type checking bugs

This commit is contained in:
Shunsuke Shibayama 2023-08-19 16:34:07 +09:00
parent a6b72ea636
commit 5affa5065f
12 changed files with 91 additions and 7 deletions

View file

@ -2307,6 +2307,10 @@ impl PyCodeGenerator {
Expr::Accessor(Accessor::Ident(ident)) if ident.vis().is_private() => { Expr::Accessor(Accessor::Ident(ident)) if ident.vis().is_private() => {
self.emit_call_local(ident, call.args) self.emit_call_local(ident, call.args)
} }
other if other.ref_t().is_type() => {
self.emit_expr(other);
self.emit_index_args(call.args);
}
other => { other => {
let is_py_api = other.is_py_api(); let is_py_api = other.is_py_api();
self.emit_push_null(); self.emit_push_null();
@ -2337,6 +2341,10 @@ impl PyCodeGenerator {
Some(7) => self.emit_with_instr_307(args), Some(7) => self.emit_with_instr_307(args),
_ => todo!("not supported Python version"), _ => todo!("not supported Python version"),
}, },
_ if local.ref_t().is_type() => {
self.emit_load_name_instr(local);
self.emit_index_args(args);
}
// "pyimport" | "py" are here // "pyimport" | "py" are here
_ => { _ => {
let is_py_api = local.is_py_api(); let is_py_api = local.is_py_api();
@ -2369,10 +2377,15 @@ impl PyCodeGenerator {
if let Some(func_name) = debind(&method_name) { if let Some(func_name) = debind(&method_name) {
return self.emit_call_fake_method(obj, func_name, method_name, args); return self.emit_call_fake_method(obj, func_name, method_name, args);
} }
let is_type = method_name.ref_t().is_type();
let is_py_api = method_name.is_py_api(); let is_py_api = method_name.is_py_api();
self.emit_expr(obj); self.emit_expr(obj);
self.emit_load_method_instr(method_name); self.emit_load_method_instr(method_name);
self.emit_args_311(args, BoundAttr, is_py_api); if is_type {
self.emit_index_args(args);
} else {
self.emit_args_311(args, BoundAttr, is_py_api);
}
} }
fn emit_var_args_311(&mut self, pos_len: usize, var_args: &PosArg) { fn emit_var_args_311(&mut self, pos_len: usize, var_args: &PosArg) {
@ -2449,6 +2462,24 @@ impl PyCodeGenerator {
self.stack_dec_n((1 + argc + kwsc) - 1); self.stack_dec_n((1 + argc + kwsc) - 1);
} }
fn emit_index_args(&mut self, mut args: Args) {
let argc = args.pos_args.len();
while let Some(arg) = args.try_remove_pos(0) {
self.emit_expr(arg.expr);
}
if argc > 1 {
self.write_instr(BUILD_TUPLE);
self.write_arg(argc);
}
self.write_instr(Opcode311::BINARY_SUBSCR);
self.write_arg(0);
if self.py_version.minor >= Some(11) {
self.write_bytes(&[0; 8]);
}
// (1 (subroutine) + argc) input objects -> 1 return object
self.stack_dec_n((1 + argc) - 1);
}
/// X.update! x -> x + 1 /// X.update! x -> x + 1
/// => X = mutate_operator((x -> x + 1)(X)) /// => X = mutate_operator((x -> x + 1)(X))
/// TODO: should be `X = X + 1` in the above case /// TODO: should be `X = X + 1` in the above case

View file

@ -999,6 +999,13 @@ impl Context {
type_.register_superclass(Obj, &obj); type_.register_superclass(Obj, &obj);
type_.register_builtin_erg_impl( type_.register_builtin_erg_impl(
FUNC_MRO, FUNC_MRO,
fn0_met(Type, array_t(Type, TyParam::erased(Nat))),
Immutable,
Visibility::BUILTIN_PUBLIC,
);
// TODO: PolyType
type_.register_builtin_erg_impl(
FUNDAMENTAL_ARGS,
array_t(Type, TyParam::erased(Nat)), array_t(Type, TyParam::erased(Nat)),
Immutable, Immutable,
Visibility::BUILTIN_PUBLIC, Visibility::BUILTIN_PUBLIC,

View file

@ -402,6 +402,7 @@ const OP_MUTATE: &str = "__mutate__";
const OP_POS: &str = "__pos__"; const OP_POS: &str = "__pos__";
const OP_NEG: &str = "__neg__"; const OP_NEG: &str = "__neg__";
const FUNDAMENTAL_ARGS: &str = "__args__";
const FUNDAMENTAL_LEN: &str = "__len__"; const FUNDAMENTAL_LEN: &str = "__len__";
const FUNDAMENTAL_CONTAINS: &str = "__contains__"; const FUNDAMENTAL_CONTAINS: &str = "__contains__";
const FUNDAMENTAL_CALL: &str = "__call__"; const FUNDAMENTAL_CALL: &str = "__call__";

View file

@ -2,6 +2,7 @@ from _erg_control import then__
from _erg_range import Range from _erg_range import Range
from _erg_nat import NatMut from _erg_nat import NatMut
from _erg_int import IntMut from _erg_int import IntMut
from _erg_contains_operator import contains_operator
class Array(list): class Array(list):
def dedup(self, same_bucket=None): def dedup(self, same_bucket=None):
@ -43,3 +44,22 @@ class Array(list):
return Array(list.__getitem__(self, index_or_slice.into_slice())) return Array(list.__getitem__(self, index_or_slice.into_slice()))
else: else:
return list.__getitem__(self, index_or_slice) return list.__getitem__(self, index_or_slice)
def type_check(self, t: type) -> bool:
if isinstance(t, list):
if len(t) != len(self):
return False
for (inner_t, elem) in zip(t, self):
if not contains_operator(inner_t, elem):
return False
return True
elif not hasattr(t, "__args__"):
return isinstance(self, t)
elem_t = t.__args__[0]
l = None if len(t.__args__) != 2 else t.__args__[1]
if l is not None and l != len(self):
return False
for elem in self:
if not contains_operator(elem_t, elem):
return False
return True

View file

@ -5,8 +5,10 @@ from collections import namedtuple
# (elem in y) == contains_operator(y, elem) # (elem in y) == contains_operator(y, elem)
def contains_operator(y, elem): def contains_operator(y, elem):
if hasattr(elem, "type_check"):
return elem.type_check(y)
# 1 in Int # 1 in Int
if type(y) == type: elif type(y) == type:
if isinstance(elem, y): if isinstance(elem, y):
return True return True
elif hasattr(y, "try_new") and is_ok(y.try_new(elem)): elif hasattr(y, "try_new") and is_ok(y.try_new(elem)):

View file

@ -7,9 +7,9 @@ class Float(float):
def try_new(i): # -> Result[Nat] def try_new(i): # -> Result[Nat]
if isinstance(i, float): if isinstance(i, float):
Float(i) return Float(i)
else: else:
Error("not a float") return Error("not a float")
def mutate(self): def mutate(self):
return FloatMut(self) return FloatMut(self)

View file

@ -5,9 +5,9 @@ from _erg_control import then__
class Int(int): class Int(int):
def try_new(i): # -> Result[Nat] def try_new(i): # -> Result[Nat]
if isinstance(i, int): if isinstance(i, int):
Int(i) return Int(i)
else: else:
Error("not an integer") return Error("not an integer")
def succ(self): def succ(self):
return Int(self + 1) return Int(self + 1)

View file

@ -2029,6 +2029,17 @@ impl Type {
} }
} }
pub fn is_type(&self) -> bool {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_type(),
Self::Refinement(refine) => refine.t.is_type(),
Self::ClassType | Self::TraitType | Self::Type => true,
Self::Quantified(q) => q.is_type(),
Self::Subr(subr) => subr.return_t.is_type(),
_ => false,
}
}
pub fn as_free(&self) -> Option<&FreeTyVar> { pub fn as_free(&self) -> Option<&FreeTyVar> {
<&FreeTyVar>::try_from(self).ok() <&FreeTyVar>::try_from(self).ok()
} }

View file

@ -12,6 +12,7 @@ Instructions that do not take arguments also use 2 bytes (the argument part is 0
* `COMPARE_OP` (6 byte) * `COMPARE_OP` (6 byte)
* `LOAD_GLOBAL` (12 byte) * `LOAD_GLOBAL` (12 byte)
* `LOAD_ATTR` (10 byte) * `LOAD_ATTR` (10 byte)
* `BINARY_SUBSCR` (8 byte)
## STORE_NAME(namei) ## STORE_NAME(namei)

View file

@ -14,6 +14,7 @@ Python bytecodeの変数操作系の命令はnamei (name index)を通してア
* `COMPARE_OP` (6 byte) * `COMPARE_OP` (6 byte)
* `LOAD_GLOBAL` (12 byte) * `LOAD_GLOBAL` (12 byte)
* `LOAD_ATTR` (10 byte) * `LOAD_ATTR` (10 byte)
* `BINARY_SUBSCR` (8 byte)
## STORE_NAME(namei) ## STORE_NAME(namei)

View file

@ -0,0 +1,5 @@
arr = ["a"]
assert arr in Array(Str)
assert arr in Array(Str, 1)
assert arr notin Array(Int)
assert arr notin Array(Str, 2)

View file

@ -22,6 +22,11 @@ fn exec_array_member() -> Result<(), ()> {
expect_success("tests/should_ok/array_member.er", 0) expect_success("tests/should_ok/array_member.er", 0)
} }
#[test]
fn exec_assert_cast_ok() -> Result<(), ()> {
expect_success("tests/should_ok/assert_cast.er", 0)
}
#[test] #[test]
fn exec_class() -> Result<(), ()> { fn exec_class() -> Result<(), ()> {
expect_success("examples/class.er", 0) expect_success("examples/class.er", 0)