fix: avoid infinite recursion

This commit is contained in:
Shunsuke Shibayama 2024-10-21 02:08:58 +09:00
parent 429e673d6c
commit 3bef190c6e
3 changed files with 51 additions and 31 deletions

View file

@ -7,7 +7,7 @@ use erg_common::dict::Dict;
use erg_common::set::Set; use erg_common::set::Set;
use erg_common::style::colors::DEBUG_ERROR; use erg_common::style::colors::DEBUG_ERROR;
use erg_common::traits::StructuralEq; use erg_common::traits::StructuralEq;
use erg_common::{assume_unreachable, fmt_vec, log, set, set_recursion_limit}; use erg_common::{assume_unreachable, fmt_vec, log, set};
use erg_common::{Str, Triple}; use erg_common::{Str, Triple};
use crate::context::eval::UndoableLinkedList; use crate::context::eval::UndoableLinkedList;
@ -359,7 +359,6 @@ impl Context {
/// 単一化、評価等はここでは行わない、スーパータイプになる **可能性があるか** だけ判定する /// 単一化、評価等はここでは行わない、スーパータイプになる **可能性があるか** だけ判定する
/// ので、lhsが(未連携)型変数の場合は単一化せずにtrueを返す /// ので、lhsが(未連携)型変数の場合は単一化せずにtrueを返す
pub(crate) fn structural_supertype_of(&self, lhs: &Type, rhs: &Type) -> bool { pub(crate) fn structural_supertype_of(&self, lhs: &Type, rhs: &Type) -> bool {
set_recursion_limit!(false, 128);
match (lhs, rhs) { match (lhs, rhs) {
// Proc :> Func if params are compatible // Proc :> Func if params are compatible
// * default params can be omitted (e.g. (Int, x := Int) -> Int <: (Int) -> Int) // * default params can be omitted (e.g. (Int, x := Int) -> Int <: (Int) -> Int)
@ -937,6 +936,11 @@ impl Context {
} }
} }
/// ```erg
/// Int.fields() == { imag: Int, real: Int, abs: (self: Int) -> Nat, ... }
/// ?T(<: Int).fields() == Int.fields()
/// Structural({ .x = Int }).fields() == { x: Int }
/// ```
pub fn fields(&self, t: &Type) -> Dict<Field, Type> { pub fn fields(&self, t: &Type) -> Dict<Field, Type> {
match t { match t {
Type::FreeVar(fv) if fv.is_linked() => self.fields(&fv.unwrap_linked()), Type::FreeVar(fv) if fv.is_linked() => self.fields(&fv.unwrap_linked()),

View file

@ -2,14 +2,13 @@
use std::iter::repeat; use std::iter::repeat;
use std::mem; use std::mem;
use std::option::Option; use std::option::Option;
use std::sync::atomic::{AtomicUsize, Ordering};
use erg_common::consts::DEBUG_MODE; use erg_common::consts::DEBUG_MODE;
use erg_common::fresh::FRESH_GEN; use erg_common::fresh::FRESH_GEN;
use erg_common::traits::Locational; use erg_common::traits::Locational;
use erg_common::Str;
#[allow(unused_imports)] #[allow(unused_imports)]
use erg_common::{dict, fmt_vec, fn_name, log}; use erg_common::{dict, fmt_vec, fn_name, log};
use erg_common::{get_hash, set_recursion_limit, Str};
use crate::context::eval::Substituter; use crate::context::eval::Substituter;
use crate::context::instantiate::TyVarCache; use crate::context::instantiate::TyVarCache;
@ -35,7 +34,6 @@ pub struct Unifier<'c, 'l, 'u, L: Locational> {
loc: &'l L, loc: &'l L,
undoable: Option<&'u UndoableLinkedList>, undoable: Option<&'u UndoableLinkedList>,
change_generalized: bool, change_generalized: bool,
recursion_limit: AtomicUsize,
param_name: Option<Str>, param_name: Option<Str>,
} }
@ -52,7 +50,6 @@ impl<'c, 'l, 'u, L: Locational> Unifier<'c, 'l, 'u, L> {
loc, loc,
undoable, undoable,
change_generalized, change_generalized,
recursion_limit: AtomicUsize::new(128),
param_name, param_name,
} }
} }
@ -1037,21 +1034,18 @@ impl<'c, 'l, 'u, L: Locational> Unifier<'c, 'l, 'u, L> {
/// ``` /// ```
fn sub_unify(&self, maybe_sub: &Type, maybe_super: &Type) -> TyCheckResult<()> { fn sub_unify(&self, maybe_sub: &Type, maybe_super: &Type) -> TyCheckResult<()> {
log!(info "trying {}sub_unify:\nmaybe_sub: {maybe_sub}\nmaybe_super: {maybe_super}", self.undoable.map_or("", |_| "undoable_")); log!(info "trying {}sub_unify:\nmaybe_sub: {maybe_sub}\nmaybe_super: {maybe_super}", self.undoable.map_or("", |_| "undoable_"));
if self.recursion_limit.fetch_sub(1, Ordering::SeqCst) == 0 { set_recursion_limit!(
self.recursion_limit.store(128, Ordering::SeqCst); panic,
log!(err "recursion limit exceeded: {maybe_sub} / {maybe_super}"); "recursion limit exceed: sub_unify({maybe_sub}, {maybe_super})",
return Err(TyCheckError::recursion_limit( 128
self.ctx.cfg.input.clone(), );
line!() as usize,
self.loc.loc(),
fn_name!(),
line!(),
)
.into());
}
// In this case, there is no new information to be gained // In this case, there is no new information to be gained
// この場合、特に新しく得られる情報はない if maybe_sub == &Type::Never
if maybe_sub == &Type::Never || maybe_super == &Type::Obj || maybe_super.addr_eq(maybe_sub) || maybe_super == &Type::Obj
|| maybe_super.addr_eq(maybe_sub)
|| (maybe_sub.has_no_unbound_var()
&& maybe_super.has_no_unbound_var()
&& maybe_sub == maybe_super)
{ {
log!(info "no-op:\nmaybe_sub: {maybe_sub}\nmaybe_super: {maybe_super}"); log!(info "no-op:\nmaybe_sub: {maybe_sub}\nmaybe_super: {maybe_super}");
return Ok(()); return Ok(());
@ -1481,8 +1475,11 @@ impl<'c, 'l, 'u, L: Locational> Unifier<'c, 'l, 'u, L> {
.inspect_err(|_e| sub_fv.undo())?; .inspect_err(|_e| sub_fv.undo())?;
} else if !self.ctx.subtype_of(&sub, &Never) { } else if !self.ctx.subtype_of(&sub, &Never) {
sub_fv.undo(); sub_fv.undo();
let sub_hash = get_hash(maybe_sub);
maybe_sub.coerce(self.undoable); maybe_sub.coerce(self.undoable);
return self.sub_unify(maybe_sub, maybe_super); if get_hash(maybe_sub) != sub_hash {
return self.sub_unify(maybe_sub, maybe_super);
}
} else { } else {
// e.g. ?T / Structural({ .method = (self: ?T) -> Int }) // e.g. ?T / Structural({ .method = (self: ?T) -> Int })
let constr = Constraint::new_sandwiched( let constr = Constraint::new_sandwiched(
@ -1518,23 +1515,27 @@ impl<'c, 'l, 'u, L: Locational> Unifier<'c, 'l, 'u, L> {
} }
let sub = mem::take(&mut sub); let sub = mem::take(&mut sub);
// min(L, R) != L and R // min(L, R) != L and R
let new_sup = if let Some(new_sup) = self.ctx.min(&supe, maybe_super).either() { let new_super = if let Some(new_sup) = self.ctx.min(&supe, maybe_super).either()
{
new_sup.clone() new_sup.clone()
} else { } else {
self.ctx.intersection(&supe, maybe_super) self.ctx.intersection(&supe, maybe_super)
}; };
self.sub_unify(&sub, &new_sup)?; if !maybe_sub.is_recursive() && (&sub != maybe_sub || &new_super != maybe_super)
{
self.sub_unify(&sub, &new_super)?;
}
// ?T(:> Int, <: Int) ==> ?T == Int // ?T(:> Int, <: Int) ==> ?T == Int
// ?T(:> List(Int, 3), <: List(?T, ?N)) ==> ?T == List(Int, 3) // ?T(:> List(Int, 3), <: List(?T, ?N)) ==> ?T == List(Int, 3)
// ?T(:> List(Int, 3), <: Indexable(?K, ?V)) ==> ?T(:> List(Int, 3), <: Indexable(0..2, Int)) // ?T(:> List(Int, 3), <: Indexable(?K, ?V)) ==> ?T(:> List(Int, 3), <: Indexable(0..2, Int))
if !sub.is_refinement() if !sub.is_refinement()
&& new_sup.qual_name() == sub.qual_name() && new_super.qual_name() == sub.qual_name()
&& !new_sup.is_unbound_var() && !new_super.is_unbound_var()
&& !sub.is_unbound_var() && !sub.is_unbound_var()
{ {
maybe_sub.link(&sub, self.undoable); maybe_sub.link(&sub, self.undoable);
} else { } else {
maybe_sub.update_tyvar(sub, new_sup, self.undoable, true); maybe_sub.update_tyvar(sub, new_super, self.undoable, true);
} }
} }
// sub_unify(?T(: Type), Int): (?T(<: Int)) // sub_unify(?T(: Type), Int): (?T(<: Int))

View file

@ -31,7 +31,7 @@ use erg_common::fresh::FRESH_GEN;
use erg_common::log; use erg_common::log;
use erg_common::set::Set; use erg_common::set::Set;
use erg_common::traits::{LimitedDisplay, Locational, StructuralEq}; use erg_common::traits::{LimitedDisplay, Locational, StructuralEq};
use erg_common::{enum_unwrap, fmt_option, ref_addr_eq, set, set_recursion_limit, Str}; use erg_common::{enum_unwrap, fmt_option, ref_addr_eq, set, Str};
use erg_parser::ast::Expr; use erg_parser::ast::Expr;
use erg_parser::token::TokenKind; use erg_parser::token::TokenKind;
@ -1005,7 +1005,7 @@ impl Eq for RefineKind {}
/// {_: StrWithLen N | N >= 0} /// {_: StrWithLen N | N >= 0}
/// {T: (Int, Int) | T.0 >= 0, T.1 >= 0} /// {T: (Int, Int) | T.0 >= 0, T.1 >= 0}
/// ``` /// ```
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone)]
pub struct RefinementType { pub struct RefinementType {
pub var: Str, pub var: Str,
pub t: Box<Type>, pub t: Box<Type>,
@ -1029,6 +1029,24 @@ impl<'a> TryFrom<&'a Type> for &'a RefinementType {
} }
} }
impl PartialEq for RefinementType {
fn eq(&self, other: &Self) -> bool {
self.t == other.t && *self.pred == other.pred.clone().change_subject_name(self.var.clone())
}
}
impl Eq for RefinementType {}
impl Hash for RefinementType {
fn hash<H: Hasher>(&self, state: &mut H) {
self.t.hash(state);
self.pred
.clone()
.change_subject_name("%RefinementType::hash".into())
.hash(state);
}
}
impl LimitedDisplay for RefinementType { impl LimitedDisplay for RefinementType {
fn limited_fmt<W: std::fmt::Write>(&self, f: &mut W, limit: isize) -> std::fmt::Result { fn limited_fmt<W: std::fmt::Write>(&self, f: &mut W, limit: isize) -> std::fmt::Result {
if limit == 0 { if limit == 0 {
@ -3616,8 +3634,6 @@ impl Type {
fv.crack().destructive_coerce(); fv.crack().destructive_coerce();
} }
Type::FreeVar(fv) if fv.is_unbound_and_sandwiched() => { Type::FreeVar(fv) if fv.is_unbound_and_sandwiched() => {
// TODO: other way to avoid infinite recursion
set_recursion_limit!({}, 128);
let (sub, _sup) = fv.get_subsup().unwrap(); let (sub, _sup) = fv.get_subsup().unwrap();
sub.destructive_coerce(); sub.destructive_coerce();
self.destructive_link(&sub); self.destructive_link(&sub);
@ -3684,7 +3700,6 @@ impl Type {
} }
} }
Type::FreeVar(fv) if fv.is_unbound_and_sandwiched() => { Type::FreeVar(fv) if fv.is_unbound_and_sandwiched() => {
set_recursion_limit!({}, 128);
let (sub, _sup) = fv.get_subsup().unwrap(); let (sub, _sup) = fv.get_subsup().unwrap();
sub.undoable_coerce(list); sub.undoable_coerce(list);
self.undoable_link(&sub, list); self.undoable_link(&sub, list);