fix: dict type bug

This commit is contained in:
Shunsuke Shibayama 2024-10-17 11:37:57 +09:00
parent b238de378d
commit 41537f2aba
9 changed files with 208 additions and 70 deletions

View file

@ -9,7 +9,7 @@ use erg_common::log;
use erg_common::set::Set;
use erg_common::shared::Shared;
use erg_common::traits::{Locational, Stream};
use erg_common::{dict, fmt_vec, fn_name, option_enum_unwrap, set, set_recursion_limit, Triple};
use erg_common::{dict, fmt_vec, fn_name, set, set_recursion_limit, Triple};
use erg_common::{ArcArray, Str};
use OpKind::*;
@ -667,10 +667,52 @@ impl Context {
}
fn tp_eval_const_call(&self, call: &Call) -> Failable<TyParam> {
if let Expr::Accessor(acc) = call.obj.as_ref() {
match acc {
Accessor::Ident(ident) => {
let obj = self.rec_get_const_obj(ident.inspect()).ok_or_else(|| {
if let Some(attr) = &call.attr_name {
let obj = self
.eval_const_expr(&call.obj)
.map_err(|(val, errs)| (TyParam::value(val), errs))?;
let callee = self
.eval_attr(obj.clone(), attr)
.map_err(|err| (TyParam::Failure, err.into()))?;
let ValueObj::Subr(subr) = callee else {
return Err((
TyParam::Failure,
EvalError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
Location::concat(call.obj.as_ref(), attr),
self.caused_by(),
attr.inspect(),
None,
&mono("Subroutine"),
&callee.t(),
self.get_candidates(&callee.t()),
None,
)
.into(),
));
};
let (mut args, mut errs) = match self.eval_args(&call.args) {
Ok(args) => (args, EvalErrors::empty()),
Err((args, es)) => (args, es),
};
args.pos_args.insert(0, obj);
let tp = match self.call(subr.clone(), args, call.loc()) {
Ok(tp) => tp,
Err((tp, es)) => {
errs.extend(es);
tp
}
};
if errs.is_empty() {
Ok(tp)
} else {
Err((tp, errs))
}
} else {
match call.obj.as_ref() {
Expr::Accessor(Accessor::Ident(ident)) => {
let callee = self.rec_get_const_obj(ident.inspect()).ok_or_else(|| {
(
TyParam::Failure,
EvalError::not_comptime_fn_error(
@ -685,31 +727,29 @@ impl Context {
)
})?;
// TODO: __call__
let subr = option_enum_unwrap!(obj, ValueObj::Subr)
.ok_or_else(|| {
(
TyParam::Failure,
EvalError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
ident.loc(),
self.caused_by(),
ident.inspect(),
None,
&mono("Subroutine"),
&obj.t(),
self.get_candidates(&obj.t()),
None,
)
.into(),
let ValueObj::Subr(subr) = callee else {
return Err((
TyParam::Failure,
EvalError::type_mismatch_error(
self.cfg.input.clone(),
line!() as usize,
ident.loc(),
self.caused_by(),
ident.inspect(),
None,
&mono("Subroutine"),
&callee.t(),
self.get_candidates(&callee.t()),
None,
)
})?
.clone();
.into(),
));
};
let (args, mut errs) = match self.eval_args(&call.args) {
Ok(args) => (args, EvalErrors::empty()),
Err((args, es)) => (args, es),
};
let tp = match self.call(subr, args, call.loc()) {
let tp = match self.call(subr.clone(), args, call.loc()) {
Ok(tp) => tp,
Err((tp, es)) => {
errs.extend(es);
@ -723,7 +763,7 @@ impl Context {
}
}
// TODO: eval attr
Accessor::Attr(_attr) => Err((
Expr::Accessor(Accessor::Attr(_attr)) => Err((
TyParam::Failure,
EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
@ -733,7 +773,7 @@ impl Context {
)),
)),
// TODO: eval type app
Accessor::TypeApp(_type_app) => Err((
Expr::Accessor(Accessor::TypeApp(_type_app)) => Err((
TyParam::Failure,
EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
@ -753,16 +793,6 @@ impl Context {
)),
)),
}
} else {
Err((
TyParam::Failure,
EvalErrors::from(EvalError::not_const_expr(
self.cfg.input.clone(),
line!() as usize,
call.loc(),
self.caused_by(),
)),
))
}
}

View file

@ -1698,11 +1698,17 @@ impl Context {
t_singleton(unknown_len_list_t(T.clone())),
)
.quantify();
generic_list.register_builtin_erg_impl(
let list_constructor = ValueObj::Subr(ConstSubr::Builtin(BuiltinConstSubr::new(
FUNDAMENTAL_GETITEM,
list_constructor,
t_getitem.clone(),
None,
)));
generic_list.register_builtin_const(
FUNDAMENTAL_GETITEM,
t_getitem,
Immutable,
Visibility::BUILTIN_PUBLIC,
Some(t_getitem),
list_constructor,
);
}
let mut list_hash = Self::builtin_methods(Some(mono(HASH)), 1);
@ -2249,11 +2255,17 @@ impl Context {
t_singleton(Type::from(dict! { T.clone() => U.clone() })),
)
.quantify();
generic_dict.register_builtin_erg_impl(
let dict_constructor = ValueObj::Subr(ConstSubr::Builtin(BuiltinConstSubr::new(
FUNDAMENTAL_GETITEM,
dict_constructor,
t_getitem.clone(),
None,
)));
generic_dict.register_builtin_const(
FUNDAMENTAL_GETITEM,
t_getitem,
Immutable,
Visibility::BUILTIN_PUBLIC,
Some(t_getitem),
dict_constructor,
);
}
let dic_t = poly(DICT, vec![D.clone()]);

View file

@ -12,7 +12,7 @@ use crate::context::eval::UndoableLinkedList;
use crate::context::initialize::closed_range;
use crate::context::Context;
use crate::feature_error;
use crate::ty::constructors::{and, mono, tuple_t, v_enum};
use crate::ty::constructors::{and, dict_mut, list_mut, mono, tuple_t, v_enum};
use crate::ty::value::{EvalValueError, EvalValueResult, GenTypeObj, TypeObj, ValueObj};
use crate::ty::{Field, TyParam, Type, ValueArgs};
use erg_common::error::{ErrorCore, ErrorKind, Location, SubMessage};
@ -483,6 +483,47 @@ pub(crate) fn dict_diff(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<T
Ok(ValueObj::Dict(slf.diff(&other)).into())
}
pub(crate) fn list_constructor(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<TyParam> {
let _cls = args
.remove_left_or_key("Cls")
.ok_or_else(|| not_passed("Cls"))?;
let elem = args
.remove_left_or_key("elem")
.ok_or_else(|| not_passed("elem"))?;
let Ok(elem_t) = ctx.convert_value_into_type(elem.clone()) else {
return Err(type_mismatch("Type", elem, "elem"));
};
let len = args
.remove_left_or_key("len")
.map(TyParam::value)
.unwrap_or(TyParam::erased(Type::Nat));
Ok(ValueObj::builtin_class(list_mut(elem_t, len)).into())
}
pub(crate) fn dict_constructor(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<TyParam> {
let _cls = args
.remove_left_or_key("Cls")
.ok_or_else(|| not_passed("Cls"))?;
let key_value = args
.remove_left_or_key("key_value")
.ok_or_else(|| not_passed("key_value"))?;
let (key_t, value_t) = match key_value {
ValueObj::Tuple(ts) | ValueObj::List(ts) => {
let key = ts.first().ok_or_else(|| not_passed("key"))?;
let value = ts.get(1).ok_or_else(|| not_passed("value"))?;
let Ok(key_t) = ctx.convert_value_into_type(key.clone()) else {
return Err(type_mismatch("Type", key, "key"));
};
let Ok(value_t) = ctx.convert_value_into_type(value.clone()) else {
return Err(type_mismatch("Type", value, "value"));
};
(key_t, value_t)
}
_ => return Err(type_mismatch("Tuple", key_value, "key_value")),
};
Ok(ValueObj::builtin_class(dict_mut(dict! { key_t => value_t }.into())).into())
}
/// `[Int, Str].union() == Int or Str`
pub(crate) fn list_union(mut args: ValueArgs, ctx: &Context) -> EvalValueResult<TyParam> {
let slf = args

View file

@ -127,6 +127,7 @@ impl Context {
None,
NoneType,
)
.quantify()
} else {
nd_proc(
vec![

View file

@ -9,7 +9,7 @@ use erg_common::fresh::FRESH_GEN;
use erg_common::traits::Locational;
use erg_common::Str;
#[allow(unused_imports)]
use erg_common::{fmt_vec, fn_name, log};
use erg_common::{dict, fmt_vec, fn_name, log};
use crate::context::eval::Substituter;
use crate::context::instantiate::TyVarCache;
@ -2075,6 +2075,47 @@ impl<'c, 'l, 'u, L: Locational> Unifier<'c, 'l, 'u, L> {
return Some(self.ctx.union_refinement(lhs, rhs).into());
}
}
(
Poly {
name: ln,
params: lps,
},
Poly {
name: rn,
params: rps,
},
) if ln == rn && (lhs.is_dict() || lhs.is_dict_mut()) => {
let Ok(ValueObj::Dict(l_dict)) = self.ctx.convert_tp_into_value(lps[0].clone())
else {
return None;
};
let Ok(ValueObj::Dict(r_dict)) = self.ctx.convert_tp_into_value(rps[0].clone())
else {
return None;
};
if l_dict.len() == 1 && r_dict.len() == 1 {
let l_key = self
.ctx
.convert_value_into_type(l_dict.keys().next()?.clone())
.ok()?;
let r_key = self
.ctx
.convert_value_into_type(r_dict.keys().next()?.clone())
.ok()?;
let l_value = self
.ctx
.convert_value_into_type(l_dict.values().next()?.clone())
.ok()?;
let r_value = self
.ctx
.convert_value_into_type(r_dict.values().next()?.clone())
.ok()?;
let unified_key = self.unify(&l_key, &r_key)?;
let unified_value = self.unify(&l_value, &r_value)?;
let unified_dict = TyParam::t(dict! { unified_key => unified_value }.into());
return Some(poly(ln.clone(), vec![unified_dict]));
}
}
_ => {}
}
let l_sups = self.ctx.get_super_classes(lhs)?;
@ -2092,16 +2133,26 @@ impl<'c, 'l, 'u, L: Locational> Unifier<'c, 'l, 'u, L> {
continue;
};
let mut tv_cache = TyVarCache::new(self.ctx.level, self.ctx);
let l_sup = self.ctx.detach(l_sup.clone(), &mut tv_cache);
let detached_l_sup = self.ctx.detach(l_sup.clone(), &mut tv_cache);
drop(l_substituter);
let Ok(r_substituter) = Substituter::substitute_typarams(self.ctx, &r_sup, rhs)
else {
continue;
};
let mut tv_cache = TyVarCache::new(self.ctx.level, self.ctx);
let r_sup = self.ctx.detach(r_sup.clone(), &mut tv_cache);
let detached_r_sup = self.ctx.detach(r_sup.clone(), &mut tv_cache);
drop(r_substituter);
if let Some(t) = self.ctx.max(&l_sup, &r_sup).either() {
if let Some(t) = self.ctx.max(&detached_l_sup, &detached_r_sup).either() {
for l_tp in l_sup.typarams() {
if l_tp.has_qvar() && t.contains_tp(&l_tp) {
return None;
}
}
for r_tp in r_sup.typarams() {
if r_tp.has_qvar() && t.contains_tp(&r_tp) {
return None;
}
}
return Some(t.clone());
}
}

View file

@ -2562,6 +2562,11 @@ impl Type {
}
}
/// ```erg
/// Int.union_size() == 1
/// (Int or Str).union_size() == 2
/// K(Int or Str, Int or Str or NoneType) == 3
/// ```
pub fn union_size(&self) -> usize {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().union_size(),
@ -3119,6 +3124,10 @@ impl Type {
|| self.contains_value(&ValueObj::Failure)
}
pub fn has_refinement(&self) -> bool {
self.is_refinement() || self.has_type_satisfies(|t| t.is_refinement())
}
pub fn is_recursive(&self) -> bool {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_recursive(),
@ -4088,6 +4097,11 @@ impl Type {
}
}
/// ```erg
/// {1, 2, 3}.derifine() == Nat
/// List({1, 2, 3}).derifine() == List(Nat)
/// ?T(:> {1, 2, 3}).derifine() == ?T'(:> Nat)
/// ```
pub fn derefine(&self) -> Type {
match self {
Self::FreeVar(fv) if fv.is_linked() => fv.crack().derefine(),
@ -4112,13 +4126,7 @@ impl Type {
Self::Poly { name, params } => {
let params = params
.iter()
.map(|tp| match tp {
TyParam::Value(ValueObj::Type(t)) => {
TyParam::Value(ValueObj::Type(t.clone().mapped_t(|t| t.derefine())))
}
TyParam::Type(t) => TyParam::t(t.derefine()),
other => other.clone(),
})
.map(|tp| tp.clone().map_t(&mut |t| t.derefine()))
.collect();
Self::Poly {
name: name.clone(),
@ -4149,22 +4157,10 @@ impl Type {
attr_name,
args,
} => {
let lhs = match lhs.as_ref() {
TyParam::Value(ValueObj::Type(t)) => {
TyParam::Value(ValueObj::Type(t.clone().mapped_t(|t| t.derefine())))
}
TyParam::Type(t) => TyParam::t(t.derefine()),
other => other.clone(),
};
let lhs = lhs.clone().map_t(&mut |t| t.derefine());
let args = args
.iter()
.map(|arg| match arg {
TyParam::Value(ValueObj::Type(t)) => {
TyParam::Value(ValueObj::Type(t.clone().mapped_t(|t| t.derefine())))
}
TyParam::Type(t) => TyParam::t(t.derefine()),
other => other.clone(),
})
.map(|arg| arg.clone().map_t(&mut |t| t.derefine()))
.collect();
proj_call(lhs, attr_name.clone(), args)
}

View file

@ -5,3 +5,7 @@ for! {"a": 1, "b": 2}.keys(), i =>
dic as {Nat: Int} = {1: -1}
_ = dic[-1] # ERR
l as List! {Str: Int} = ![{"a": 1}]
l.push!({"b": 2}) # OK
l.push!({1: "a"}) # ERR

View file

@ -13,3 +13,6 @@ assert rec.a == 1 and rec.b == 2
gdic as {Obj: Obj} = dic
assert "a" in gdic
assert not 1 in gdic
l as List! {Str: Int} = ![{"a": 1}]
l.push!({"b": 2}) # OK

View file

@ -599,7 +599,7 @@ fn exec_dependent_err() -> Result<(), ()> {
#[test]
fn exec_dict_err() -> Result<(), ()> {
expect_compile_failure("tests/should_err/dict.er", 0, 3)
expect_compile_failure("tests/should_err/dict.er", 0, 4)
}
#[test]