mirror of
https://github.com/erg-lang/erg.git
synced 2025-09-28 12:14:43 +00:00
fix: union types bug & multi-pattern def bug
This commit is contained in:
parent
7c8b8a66a1
commit
fc85265d9f
14 changed files with 299 additions and 88 deletions
|
@ -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)
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
@ -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} = ...`"),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue