fix: union types bug & multi-pattern def bug

This commit is contained in:
Shunsuke Shibayama 2023-04-10 22:26:46 +09:00
parent 7c8b8a66a1
commit fc85265d9f
14 changed files with 299 additions and 88 deletions

View file

@ -8,7 +8,7 @@ use erg_common::traits::StructuralEq;
use erg_common::Str;
use erg_common::{assume_unreachable, log};
use crate::ty::constructors::{and, not, or, poly};
use crate::ty::constructors::{and, bounded, not, or, poly};
use crate::ty::free::{Constraint, FreeKind};
use crate::ty::typaram::{OpKind, TyParam, TyParamOrdering};
use crate::ty::value::ValueObj;
@ -982,6 +982,18 @@ impl Context {
}
(Refinement(l), Refinement(r)) => Type::Refinement(self.union_refinement(l, r)),
(Structural(l), Structural(r)) => self.union(l, r).structuralize(),
// Int..Obj or Nat..Obj ==> Int..Obj
// Str..Obj or Int..Obj ==> Str..Obj or Int..Obj
(
Bounded { sub, sup },
Bounded {
sub: sub2,
sup: sup2,
},
) => match (self.max(sub, sub2), self.min(sup, sup2)) {
(Some(sub), Some(sup)) => bounded(sub.clone(), sup.clone()),
_ => self.simple_union(lhs, rhs),
},
(t, Type::Never) | (Type::Never, t) => t.clone(),
// Array({1, 2}, 2), Array({3, 4}, 2) ==> Array({1, 2, 3, 4}, 2)
(
@ -997,26 +1009,10 @@ impl Context {
debug_assert_eq!(lps.len(), rps.len());
let mut unified_params = vec![];
for (lp, rp) in lps.iter().zip(rps.iter()) {
match (lp, rp) {
(TyParam::Value(ValueObj::Type(l)), TyParam::Value(ValueObj::Type(r))) => {
unified_params.push(TyParam::t(self.union(l.typ(), r.typ())));
}
(TyParam::Value(ValueObj::Type(l)), TyParam::Type(r)) => {
unified_params.push(TyParam::t(self.union(l.typ(), r)));
}
(TyParam::Type(l), TyParam::Value(ValueObj::Type(r))) => {
unified_params.push(TyParam::t(self.union(l, r.typ())));
}
(TyParam::Type(l), TyParam::Type(r)) => {
unified_params.push(TyParam::t(self.union(l, r)));
}
(_, _) => {
if self.eq_tp(lp, rp) {
unified_params.push(lp.clone());
} else {
return self.simple_union(lhs, rhs);
}
}
if let Some(union) = self.union_tp(lp, rp) {
unified_params.push(union);
} else {
return self.simple_union(lhs, rhs);
}
}
poly(ln, unified_params)
@ -1025,6 +1021,39 @@ impl Context {
}
}
fn union_tp(&self, lhs: &TyParam, rhs: &TyParam) -> Option<TyParam> {
match (lhs, rhs) {
(TyParam::Value(ValueObj::Type(l)), TyParam::Value(ValueObj::Type(r))) => {
Some(TyParam::t(self.union(l.typ(), r.typ())))
}
(TyParam::Value(ValueObj::Type(l)), TyParam::Type(r)) => {
Some(TyParam::t(self.union(l.typ(), r)))
}
(TyParam::Type(l), TyParam::Value(ValueObj::Type(r))) => {
Some(TyParam::t(self.union(l, r.typ())))
}
(TyParam::Type(l), TyParam::Type(r)) => Some(TyParam::t(self.union(l, r))),
(TyParam::Array(l), TyParam::Array(r)) => {
let mut tps = vec![];
for (l, r) in l.iter().zip(r.iter()) {
if let Some(tp) = self.union_tp(l, r) {
tps.push(tp);
} else {
return None;
}
}
Some(TyParam::Array(tps))
}
(_, _) => {
if self.eq_tp(lhs, rhs) {
Some(lhs.clone())
} else {
None
}
}
}
}
fn simple_union(&self, lhs: &Type, rhs: &Type) -> Type {
// `?T or ?U` will not be unified
// `Set!(?T(<: Int), 3) or Set(?U(<: Nat), 3)` wii be unified to Set(?T, 3)

View file

@ -21,7 +21,7 @@ use crate::ty::constructors::{
array_t, dict_t, mono, poly, proj, proj_call, ref_, ref_mut, refinement, subr_t, tuple_t,
v_enum,
};
use crate::ty::free::{Constraint, HasLevel};
use crate::ty::free::{Constraint, FreeTyVar, HasLevel};
use crate::ty::typaram::{OpKind, TyParam};
use crate::ty::value::{GenTypeObj, TypeObj, ValueObj};
use crate::ty::{ConstSubr, HasType, Predicate, SubrKind, Type, UserConstSubr, ValueArgs};
@ -1275,7 +1275,7 @@ impl Context {
let t = self
.convert_tp_into_type(params[0].clone())
.map_err(|_| ())?;
let len = enum_unwrap!(params[1], TyParam::Value:(ValueObj::Nat:(_)));
let TyParam::Value(ValueObj::Nat(len)) = params[1] else { unreachable!() };
Ok(vec![ValueObj::builtin_type(t); len as usize])
}
_ => Err(()),
@ -1432,7 +1432,7 @@ impl Context {
Ok(())
}
TyParam::Type(gt) if gt.is_generalized() => {
let qt = enum_unwrap!(gt.as_ref(), Type::FreeVar);
let Ok(qt) = <&FreeTyVar>::try_from(gt.as_ref()) else { unreachable!() };
let Ok(st) = Type::try_from(stp) else { todo!(); };
if !st.is_generalized() {
qt.undoable_link(&st);
@ -1442,7 +1442,7 @@ impl Context {
TyParam::Type(qt) => {
let Ok(st) = Type::try_from(stp) else { todo!(); };
let st = if st.typarams_len() != qt.typarams_len() {
let st = enum_unwrap!(st, Type::FreeVar);
let Ok(st) = <&FreeTyVar>::try_from(&st) else { unreachable!() };
st.get_sub().unwrap()
} else {
st
@ -1461,7 +1461,7 @@ impl Context {
match tp {
TyParam::FreeVar(fv) if fv.is_undoable_linked() => fv.undo(),
TyParam::Type(t) if t.is_free_var() => {
let subst = enum_unwrap!(t.as_ref(), Type::FreeVar);
let Ok(subst) = <&FreeTyVar>::try_from(t.as_ref()) else { unreachable!() };
if subst.is_undoable_linked() {
subst.undo();
}

View file

@ -1213,4 +1213,32 @@ impl Context {
hir::Expr::Import(_) => unreachable!(),
}
}
/// ```erg
/// squash_tyvar(?1 or ?2) == ?1(== ?2)
/// squash_tyvar(?T or ?U) == ?T or ?U
/// ```
pub(crate) fn squash_tyvar(&self, typ: Type) -> Type {
match typ {
Type::Or(l, r) => {
let l = self.squash_tyvar(*l);
let r = self.squash_tyvar(*r);
if l.is_named_unbound_var() && r.is_named_unbound_var() {
self.union(&l, &r)
} else {
match (self.subtype_of(&l, &r), self.subtype_of(&r, &l)) {
(true, true) | (true, false) => {
let _ = self.sub_unify(&l, &r, &(), None);
}
(false, true) => {
let _ = self.sub_unify(&r, &l, &(), None);
}
_ => {}
}
self.union(&l, &r)
}
}
other => other,
}
}
}

View file

@ -656,8 +656,16 @@ impl Context {
TyCheckErrors::new(
errs.into_iter()
.map(|e| {
let expect = self.readable_type(spec_ret_t.clone());
let found = self.readable_type(body_t.clone());
let expect = if cfg!(feature = "debug") {
spec_ret_t.clone()
} else {
self.readable_type(spec_ret_t.clone())
};
let found = if cfg!(feature = "debug") {
body_t.clone()
} else {
self.readable_type(body_t.clone())
};
TyCheckError::return_type_error(
self.cfg.input.clone(),
line!() as usize,

View file

@ -30,6 +30,8 @@ impl Context {
/// occur(X -> ?T, X -> ?T) ==> OK
/// occur(?T, ?T -> X) ==> Error
/// occur(?T, Option(?T)) ==> Error
/// occur(?T or ?U, ?T) ==> OK
/// occur(?T(<: Str) or ?U(<: Int), ?T(<: Str)) ==> Error
/// occur(?T, ?T.Output) ==> OK
pub(crate) fn occur(
&self,
@ -118,6 +120,10 @@ impl Context {
}
Ok(())
}
(Or(l, r), Or(l2, r2)) | (And(l, r), And(l2, r2)) => {
self.occur(l, l2, loc)?;
self.occur(r, r2, loc)
}
(lhs, Or(l, r)) | (lhs, And(l, r)) => {
self.occur_inner(lhs, l, loc)?;
self.occur_inner(lhs, r, loc)
@ -787,14 +793,29 @@ impl Context {
self.caused_by(),
)));
};
if sub_fv.level().unwrap_or(GENERIC_LEVEL)
<= sup_fv.level().unwrap_or(GENERIC_LEVEL)
match sub_fv
.level()
.unwrap_or(GENERIC_LEVEL)
.cmp(&sup_fv.level().unwrap_or(GENERIC_LEVEL))
{
sub_fv.update_constraint(new_constraint, false);
sup_fv.link(maybe_sub);
} else {
sup_fv.update_constraint(new_constraint, false);
sub_fv.link(maybe_sup);
std::cmp::Ordering::Less => {
sub_fv.update_constraint(new_constraint, false);
sup_fv.link(maybe_sub);
}
std::cmp::Ordering::Greater => {
sup_fv.update_constraint(new_constraint, false);
sub_fv.link(maybe_sup);
}
std::cmp::Ordering::Equal => {
// choose named one
if sup_fv.is_named_unbound() {
sup_fv.update_constraint(new_constraint, false);
sub_fv.link(maybe_sup);
} else {
sub_fv.update_constraint(new_constraint, false);
sup_fv.link(maybe_sub);
}
}
}
Ok(())
}

View file

@ -1024,6 +1024,7 @@ impl LowerWarning {
fn_name: &str,
typ: &Type,
) -> Self {
let fn_name = fn_name.with_color(Color::Yellow);
let hint = switch_lang!(
"japanese" => format!("`{fn_name}(...): {typ} = ...`など明示的に戻り値型を指定してください"),
"simplified_chinese" => format!("请明确指定函数{fn_name}的返回类型,例如`{fn_name}(...): {typ} = ...`"),

View file

@ -10,14 +10,18 @@ def in_operator(elem, y):
return True
# TODO: trait check
return False
elif issubclass(type(y), list) and (
type(y[0]) == type or issubclass(type(y[0]), Range)
elif isinstance(y, list) and (
type(y[0]) == type or isinstance(y[0], Range)
):
# FIXME:
type_check = in_operator(elem[0], y[0])
len_check = len(elem) == len(y)
return type_check and len_check
elif issubclass(type(y), dict) and issubclass(type(next(iter(y.keys()))), type):
elif isinstance(y, tuple):
type_check = all(map(lambda x: in_operator(x[0], x[1]), zip(elem, y)))
len_check = len(elem) == len(y)
return type_check and len_check
elif isinstance(y, dict) and isinstance(next(iter(y.keys())), type):
# TODO:
type_check = True # in_operator(x[next(iter(x.keys()))], next(iter(y.keys())))
len_check = len(elem) >= len(y)

View file

@ -16,7 +16,7 @@ use crate::ty::{HasType, Type, ValueObj, VisibilityModifier};
use crate::error::{
CompileErrors, LowerError, LowerResult, LowerWarning, LowerWarnings, SingleLowerResult,
};
use crate::hir::{self, Expr, HIR};
use crate::hir::{self, Expr, Signature, HIR};
use crate::lower::ASTLowerer;
use crate::varinfo::VarInfo;
@ -279,4 +279,47 @@ impl ASTLowerer {
self.check_doc_comments(&hir);
self.module.context.pop();
}
pub(crate) fn warn_implicit_union(&mut self, hir: &HIR) {
for chunk in hir.module.iter() {
self.warn_implicit_union_chunk(chunk);
}
}
fn warn_implicit_union_chunk(&mut self, chunk: &Expr) {
match chunk {
Expr::ClassDef(class_def) => {
for chunk in class_def.methods.iter() {
self.warn_implicit_union_chunk(chunk);
}
}
Expr::PatchDef(patch_def) => {
for chunk in patch_def.methods.iter() {
self.warn_implicit_union_chunk(chunk);
}
}
Expr::Def(def) => {
if let Signature::Subr(subr) = &def.sig {
let return_t = subr.ref_t().return_t().unwrap();
if return_t.union_pair().is_some() && subr.return_t_spec.is_none() {
let typ = if cfg!(feature = "debug") {
return_t.clone()
} else {
self.module.context.readable_type(return_t.clone())
};
let warn = LowerWarning::union_return_type_warning(
self.input().clone(),
line!() as usize,
subr.loc(),
self.module.context.caused_by(),
subr.ident.inspect(),
&typ,
);
self.warns.push(warn);
}
}
}
_ => {}
}
}
}

View file

@ -1582,25 +1582,13 @@ impl ASTLowerer {
}
match self.lower_block(body.block) {
Ok(block) => {
let found_body_t = block.ref_t();
let found_body_t = self.module.context.squash_tyvar(block.t());
let vi = self.module.context.outer.as_mut().unwrap().assign_subr(
&sig,
body.id,
found_body_t,
&found_body_t,
block.last().unwrap(),
)?;
let return_t = vi.t.return_t().unwrap();
if return_t.union_pair().is_some() && sig.return_t_spec.is_none() {
let warn = LowerWarning::union_return_type_warning(
self.input().clone(),
line!() as usize,
sig.loc(),
self.module.context.caused_by(),
sig.ident.inspect(),
&self.module.context.readable_type(return_t.clone()),
);
self.warns.push(warn);
}
let ident = hir::Identifier::new(sig.ident, None, vi);
let sig =
hir::SubrSignature::new(ident, sig.bounds, params, sig.return_t_spec);
@ -2519,6 +2507,7 @@ impl ASTLowerer {
return Err(self.return_incomplete_artifact(hir));
}
};
self.warn_implicit_union(&hir);
self.warn_unused_expr(&hir.module, mode);
self.warn_unused_vars(mode);
self.check_doc_comments(&hir);

View file

@ -462,6 +462,14 @@ impl<T> FreeKind<T> {
}
}
}
pub const fn is_named_unbound(&self) -> bool {
matches!(self, Self::NamedUnbound { .. })
}
pub const fn is_undoable_linked(&self) -> bool {
matches!(self, Self::UndoableLinked { .. })
}
}
#[derive(Debug, Clone)]
@ -753,14 +761,15 @@ impl<T> Free<T> {
}
pub fn is_linked(&self) -> bool {
matches!(
&*self.borrow(),
FreeKind::Linked(_) | FreeKind::UndoableLinked { .. }
)
self.borrow().linked().is_some()
}
pub fn is_undoable_linked(&self) -> bool {
matches!(&*self.borrow(), FreeKind::UndoableLinked { .. })
self.borrow().is_undoable_linked()
}
pub fn is_named_unbound(&self) -> bool {
self.borrow().is_named_unbound()
}
pub fn unsafe_crack(&self) -> &T {

View file

@ -2081,6 +2081,22 @@ impl Type {
matches!(self, Self::FreeVar(fv) if fv.is_unbound() || fv.crack().is_unbound_var())
}
pub fn is_named_unbound_var(&self) -> bool {
matches!(self, Self::FreeVar(fv) if fv.is_named_unbound() || (fv.is_linked() && fv.crack().is_named_unbound_var()))
}
pub fn is_totally_unbound(&self) -> bool {
match self {
Self::FreeVar(fv) if fv.is_unbound() => true,
Self::FreeVar(fv) if fv.is_linked() => fv.crack().is_totally_unbound(),
Self::Or(t1, t2) | Self::And(t1, t2) => {
t1.is_totally_unbound() && t2.is_totally_unbound()
}
Self::Not(t) => t.is_totally_unbound(),
_ => false,
}
}
/// See also: `is_monomorphized`
pub fn is_monomorphic(&self) -> bool {
matches!(self.typarams_len(), Some(0) | None)